【问题标题】:How to make async / await in Swift?如何在 Swift 中进行异步/等待?
【发布时间】:2018-07-20 16:08:06
【问题描述】:

我想模拟从 Javascript 到 Swift 4 的 async 和 await 请求。我搜索了很多关于如何做到这一点,我想我找到了 DispatchQueue 的答案,但我不明白它是如何工作的。

我想做一件简单的事情:

if let items = result.value {
    var availableBornes = [MGLPointFeature]()

    for item in items {
        guard let id = item.id else { continue }

        let coordinate = CLLocationCoordinate2D(latitude: Double(coor.x), longitude: Double(coor.y))

        // ...

        // This is an asynchronous request I want to wait
        await _ = directions.calculate(options) { (waypoints, routes, error) in
            guard error == nil else {
                print("Error calculating directions: \(error!)")
                return
            }

            // ...

            if let route = routes?.first {
                let distanceFormatter = LengthFormatter()
                let formattedDistance = distanceFormatter.string(fromMeters: route.distance)
                item.distance = formattedDistance

                // Save feature
                let feature = MGLPointFeature()

                feature.attributes = [
                    "id": id,
                    "distance": formattedDistance
                ]

                availableBornes.append(feature)

            }
        }
    }

    // This should be called after waiting for the async requests
    self.addItemsToMap(availableBornes: availableBornes)
}

我该怎么办?

【问题讨论】:

  • 您的问题中没有 Javascript。您发布的 Swift 代码到底有什么问题?你的问题很不清楚。
  • 别等了。请学习理解 Swift 中的异步数据处理。在您的示例中,只需将行 self.addItemsToMap(availableBornes: availableBornes) 放入完成块。
  • 不,我不能,因为我必须等待 for 完成。我的异步查询将被调用 items.count 次。
  • 然后使用 DispatchGroup 。它提供enterleave 语句,并且可以在最后一次迭代完成时notify。请看stackoverflow.com/questions/44912000/…
  • 听起来你想要一个 DispatchGroup。一旦你尝试了普通的计算机代码,你就再也不会使用“承诺”了:-O ;)

标签: ios swift asynchronous async-await


【解决方案1】:

(注:Swift 5 may support await as you’d expect it in ES6!

您要研究的是 Swift 的“闭包”概念。这些以前在 Objective-C 中称为“块”或完成处理程序。

JavaScript 和 Swift 的相似之处在于两者都允许您将“回调”函数传递给另一个函数,并在长时间运行的操作完成时执行它。例如,在 Swift 中:

func longRunningOp(searchString: String, completion: (result: String) -> Void) {
    // call the completion handler/callback function
    completion(searchOp.result)
}
longRunningOp(searchString) {(result: String) in
    // do something with result
}        

在 JavaScript 中应该是这样的:

var longRunningOp = function (searchString, callback) {
    // call the callback
    callback(err, result)
}
longRunningOp(searchString, function(err, result) {
    // Do something with the result
})

还有一些库,特别是谷歌的一个新库,它将闭包转换为承诺:https://github.com/google/promises。这些可能会让您与awaitasync 更接近。

【讨论】:

  • 谢谢!我考虑过使用回调,但我的代码有点复杂,因为我有一个包含异步请求的 for 循环。在异步请求完成后,我想在 for 循环之外执行代码。我不知道如何在这里使用回调,因为我应该多次调用它。你明白我的意思吗?
  • 是的,那么您正在寻找的是一个承诺。检查那里的链接。
【解决方案2】:

感谢vadian 的评论,我找到了我所期望的,这很容易。我使用DispatchGroup()group.enter()group.leave()group.notify(queue: .main){}

func myFunction() {
    let array = [Object]()
    let group = DispatchGroup() // initialize

    array.forEach { obj in

        // Here is an example of an asynchronous request which use a callback
        group.enter() // wait
        LogoRequest.init().downloadImage(url: obj.url) { (data) in
            if (data) {
                group.leave() // continue the loop
            }
        }
    }

    group.notify(queue: .main) {
        // do something here when loop finished
    }
}

【讨论】:

  • 这也是非回答,因为该函数会在group.notify中的代码运行之前返回
【解决方案3】:

您可以使用信号量来模拟 async/await。

func makeAPICall() -> Result <String?, NetworkError> {
            let path = "https://jsonplaceholder.typicode.com/todos/1"
            guard let url = URL(string: path) else {
                return .failure(.url)
            }
            var result: Result <String?, NetworkError>!
            
            let semaphore = DispatchSemaphore(value: 0)
            URLSession.shared.dataTask(with: url) { (data, _, _) in
                if let data = data {
                    result = .success(String(data: data, encoding: .utf8))
                } else {
                    result = .failure(.server)
                }
                semaphore.signal()
            }.resume()
            _ = semaphore.wait(wallTimeout: .distantFuture)
            return result
 }

下面是它如何处理连续 API 调用的示例:

func load() {
        DispatchQueue.global(qos: .utility).async {
           let result = self.makeAPICall()
                .flatMap { self.anotherAPICall($0) }
                .flatMap { self.andAnotherAPICall($0) }
            
            DispatchQueue.main.async {
                switch result {
                case let .success(data):
                    print(data)
                case let .failure(error):
                    print(error)
                }
            }
        }
    }

这里是article 的详细描述。

你也可以在 PromiseKit 和类似的库中使用 Promise

【讨论】:

    【解决方案4】:

    您可以将此框架用于 Swift 协程 - https://github.com/belozierov/SwiftCoroutine

    与 DispatchSemaphore 不同,当你调用 await 时,它不会阻塞线程,只会挂起协程,所以你也可以在主线程中使用它。

    func awaitAPICall(_ url: URL) throws -> String? {
        let future = URLSession.shared.dataTaskFuture(for: url)
        let data = try future.await().data
        return String(data: data, encoding: .utf8)
    }
    
    func load(url: URL) {
        DispatchQueue.main.startCoroutine {
            let result1 = try self.awaitAPICall(url)
            let result2 = try self.awaitAPICall2(result1)
            let result3 = try self.awaitAPICall3(result2)
            print(result3)
        }
    }
    

    【讨论】:

      【解决方案5】:

      在 iOS 13 及更高版本中,您现在可以使用 Combine 来执行此操作。 Future 类似于async 并且发布者上的flatMap 运算符(Future 是发布者)类似于await。这是一个大致基于您的代码的示例:

      Future<Feature, Error> { promise in
        directions.calculate(options) { (waypoints, routes, error) in
           if let error = error {
             promise(.failure(error))
           }
      
           promise(.success(routes))
        }
       }
       .flatMap { routes in 
         // extract feature from routes here...
         feature
       }
       .receiveOn(DispatchQueue.main) // UI updates should run on the main queue
       .sink(receiveCompletion: { completion in
          // completion is either a .failure or it's a .success holding
          // the extracted feature; if the process above was successful, 
          // you can now add feature to the map
       }, receiveValue: { _ in })
       .store(in: &self.cancellables)
      

      编辑:我在this blog post 中进行了更详细的说明。

      【讨论】:

      • 这不允许你“等待”任何东西,发生这种情况的函数仍然会异步发生,这意味着你不能从这个代码所在的函数返回结果。这使得这个没有回答问题
      【解决方案6】:

      我们必须等待!

      async-await Swift Evolution 提案 SE-0296 async/await 最近在 2020 年 12 月 24 日经过 2 次推介和修订修改后被接受。这意味着我们将能够在 Swift 5.5 中使用该功能。延迟的原因是Objective-C 的向后兼容性问题,请参阅SE-0297 与Objective-C 的并发互操作性。引入这么大的语言特性有很多副作用和依赖,所以我们现在只能使用实验工具链。因为 SE-0296 有 2 个修订版,SE-0297 实际上在 SE-0296 之前就被接受了。

      一般用途

      我们可以使用以下语法定义一个异步函数:

      private func raiseHand() async -> Bool {
        sleep(3)
        return true
      }
      

      这里的想法是在返回类型旁边包含 async 关键字,因为如果我们使用新的await 关键字。

      要等待函数完成,我们可以使用await

      let result = await raiseHand()
      

      同步/异步

      将同步函数定义为异步只能向前兼容 - 我们不能将异步函数声明为同步。这些规则适用于函数变量语义,也适用于作为作为参数或作为属性本身传递的闭包

      var syncNonThrowing: () -> Void
      var asyncNonThrowing: () async -> Void
      ...
      asyncNonThrowing = syncNonThrowing // This is OK.
      

      投掷函数

      同样的一致性约束应用于抛出函数,其方法签名中带有throws,只要函数本身是async,我们就可以使用@autoclosures

      我们也可以在等待抛出async 函数时使用try 变体,例如try?try!,作为标准的Swift 语法。

      rethrows 遗憾的是,由于async 之间的ABI 差异,它仍然需要经过提案审核才能被纳入方法实现和更薄的rethrows ABI(Apple 希望延迟集成直到通过单独的提案解决低效率问题)。

      网络回调

      这是 async/await经典用例,也是您需要修改代码的地方:

      // This is an asynchronous request I want to wait
      await _ = directions.calculate(options) { (waypoints, routes, error) in
      

      改为这个

      func calculate(options: [String: Any]) async throws -> ([Waypoint], Route) {
          let (data, response) = try await session.data(from: newURL)
          // Parse waypoints, and route from data and response.
          // If we get an error, we throw.
          return (waypoints, route)
      }
      ....
      let (waypoints, routes) = try await directions.calculate(options)
      // You can now essentially move the completion handler logic out of the closure and into the same scope as `.calculate(:)`
      

      NSURLSession.dataTask 等异步网络方法现在具有用于 async/await 的 asynchronous alternatives。但是,异步函数不会在完成块中传递错误,而是会抛出错误。因此,我们必须使用try await 来启用抛出行为。 SE-0297 使这些更改成为可能,因为 NSURLSession 属于 Foundation,它仍然主要是 Objective-C

      代码影响

      • 这个功能真的清理了代码库,再见 末日金字塔 ?!

      • 除了清理代码库外,我们还改进了嵌套网络回调的错误处理,因为错误和结果是分开的。

      • 我们可以连续使用多个await语句来减少对DispatchGroup的依赖。 ? 线程死锁在不同 DispatchQueues 之间同步 DispatchGroups 时。

      • 不易出错,因为 API 更清晰易读。不考虑完成处理程序中的所有退出路径,并且条件分支意味着可能会产生一些在编译时未发现的细微错误。

      • async / await 不能反向部署到运行 if #available(iOS 13, *) 检查支持旧设备的位置。对于较旧的操作系统版本,我们仍然需要使用 GCD。

      【讨论】:

        【解决方案7】:

        现在 Swift 正式支持 Async/await。

        会是这样的

        func myFunction() async throws {
            let array: [Object] = getObjects()
        
            let images = try await withThrowingTaskGroup(of: Data.self, returning: [Data].self) { group in
                array.forEach { object in
                    group.async {
                        try await LogoRequest().downloadImage(url: object.url)
                    }
                }
                return try await group.reduce([], {$0 + [$1]})
            }
            // at this point all `downloadImage` are done, and `images` is populated
            _ = images
        }
        

        【讨论】:

          【解决方案8】:

          像下面这样使用 async/await,

          enum DownloadError: Error {
          
          case statusNotOk
          case decoderError
          
          }
          

          方法调用

          override func viewDidLoad()  {
              super.viewDidLoad()
              async{
                  do {
                      let data =  try await fetchData()
                      do{
                          let json = try JSONSerialization.jsonObject(with: data, options:.mutableLeaves)
                          print(json)
                      }catch{
                          print(error.localizedDescription)
                      }
                  }catch{
                      print(error.localizedDescription)
                  }
                  
              }
          }
          
          func fetchData() async throws -> Data{
              
              let url = URL(string: "https://www.gov.uk/bank-holidays.json")!
              let request = URLRequest(url:url)
              let (data,response) = try await URLSession.shared.data(for: request)
              
              guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else{
                  
                  throw DownloadError.statusNotOk
              }
              return data
              
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-10-13
            • 2017-04-16
            • 2017-07-22
            • 2021-10-22
            • 2019-03-04
            • 1970-01-01
            • 2021-12-01
            • 2019-02-10
            相关资源
            最近更新 更多