【问题标题】:Idiomatic Way to Mutate Collection Asynchronously in Swift在 Swift 中异步改变集合的惯用方式
【发布时间】:2020-07-01 00:38:34
【问题描述】:

在 Swift 中异步改变字典/其他集合的惯用正确方法是什么?

编码时经常会出现以下几种情况:

func loadData(key: String, dict: inout [String: String]) {
    // Load some data. Use DispatchQueue to simulate async request
    DispatchQueue.main.async {
        dict[key] = "loadedData"
    }
}

var dict = [String:String]()

for x in ["a", "b", "c"] {
    loadData(key: x, dict: &dict)
}

在这里,我异步加载一些数据并将其添加到作为参数传入的集合中。

但是,由于inout 的复制语义,此代码无法在 Swift 中编译。

我已经想到了两个解决这个问题的方法:

  1. 将字典包装在一个类中,然后将这个类传递给函数。然后我可以改变这个类,因为它不是一个值类型。
  2. 使用不安全的指针

执行此操作的惯用正确方法是什么?

我看到这个问题中有一些关于这个话题的讨论:Inout parameter in async callback does not work as expected。但是,没有一个答案集中在如何实际解决问题上,只是为什么现在的代码不起作用。

【问题讨论】:

    标签: ios swift asynchronous


    【解决方案1】:

    这个(黑客)似乎有效:

    func loadData(key: String, applyChanges: @escaping ((inout [String: String]) -> Void) -> Void) {
        DispatchQueue.main.async {
            applyChanges { dict in
                dict[key] = "loadedData"
            }
        }
    }
    
    ...
    
    for x in ["a", "b", "c"] {
        loadData(key: x) { $0(&dict) }
    }
    

    虽然不是惯用的...我想说这里惯用的做法是异步地改变事物。您始终可以将您想要做的更改返回到完成处理程序中的集合。就字典而言,这可能是另一个字典,然后您将 merge 与原始字典一起使用。

    【讨论】:

      【解决方案2】:

      将异步更新放在loadData 中似乎很方便,但它很脆弱,并且对我们与该字典的交互方式施加了限制:

      • 它将我们的网络代码与我们的模型和业务逻辑纠缠在一起(loadData 仅适用于字典;它假设在其他线程上没有与字典的其他交互;它假设我们实际上并未与本地商店合作,等等)。

      • 它无法跟踪多个请求并知道它们何时完成;等等)。

      虽然理论上您可以通过为集合提供线程安全包装器来解决这些问题(将同步和异步更新移动到可以随处访问的层,而不仅仅是从网络层访问),但我们确实需要我们的网络代码与我们用于更新模型和/或本地存储的代码更松散地耦合。

      惯用的解决方案是loadData 应该简单地将其角色限制为仅检索数据。这是它的唯一责任。本地模型或本地存储的更新不应涉及从服务器请求数据的例程。它应该只检索数据并通过完成处理程序将这些信息提供给调用者:

      func loadData(key: String, completion: @escaping (Result<String, Error>) -> Void) {
          // Load some data. Use DispatchQueue to simulate async request
          DispatchQueue.main.async {
              completion(.success(resultString))
          }
      }
      

      然后调用者会更新集合。

      let group = DispatchGroup()
      
      for x in ["a", "b", "c"] {
          group.enter()
          loadData(key: x) { result in 
              defer { group.leave() }
      
              switch result {
              case .success(let value): dict[x] = value
              case .failure(let error): ...
              }
          }
      }
      
      group.notify(queue: .main) {
          // all done updating collection here
      }
      

      但通常会努力避免将网络类与更新模型的这一层紧密耦合。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-12
        • 1970-01-01
        • 1970-01-01
        • 2012-04-10
        • 1970-01-01
        • 1970-01-01
        • 2014-06-01
        相关资源
        最近更新 更多