繁体   English   中英

如何在 Swift 中进行异步/等待?

[英]How to make async / await in Swift?

我想模拟从 Javascript 到 Swift 的异步和等待请求 4. 我搜索了很多有关如何执行此操作的信息,我认为我找到了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)
}

我应该怎么办?

感谢vdian的评论,我找到了我所期望的,而且很容易。 我使用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
    }
}

我们必须等待!

Swift Evolution 提案SE-0296 async/await在最近202012 月 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关键字,调用站点将在完成时返回(此处为BOOL

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

let result = await raiseHand()

同步/异步

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

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

投掷功能

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

我们还可以使用try变体,例如try? try! 每当我们等待抛出async函数时,就像标准的 Swift 语法一样。

不幸的是, rethrows在合并之前仍然需要通过提案审查,因为async方法实现和更薄的rethrows ABI 之间存在根本的 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 的异步替代方案 但是,异步函数不会在完成块中传递错误而是会抛出错误。 因此,我们必须使用try await来启用抛出行为。 由于NSURLSession属于仍然主要是 Objective-C 的Foundation ,因此SE- NSURLSession使这些更改成为可能。

代码影响

  • 此功能确实清理了代码库,再见末日金字塔👋!

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

  • 我们可以连续使用多个await语句来减少对DispatchGroup的依赖。 👋 跨不同DispatchQueue同步DispatchGroup线程死锁

  • 不易出错,因为 API 更易于阅读。 不考虑完成处理程序的所有退出路径条件分支意味着可能会累积在编译时未捕获的细微错误。

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

(注意: Swift 5 可能支持await正如您在 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

您可以使用信号量来模拟 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)
                }
            }
        }
    }

这是详细描述它的文章

您还可以将 Promise 与 PromiseKit 和类似的库一起使用

您可以将此框架用于 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)
    }
}

在 iOS 13 及更高版本中,您现在可以使用组合来执行此操作。 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)

编辑:我在这篇博文中详细介绍了。

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
}

像这样在下面使用 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
    
}

在这里你可以看到一个基本的区别,callBack 如何被 async/await 替换

在这里你可以看到一个基本的区别,callBack 如何被 async/await 替换

基本上,场景A和场景B=通过闭包调用API。 场景 C 和场景 D= 通过 Async/Await 调用 API。 场景 E = Serial API 通过嵌套闭包调用。 场景 F= Serial API 由 Async/Await 调用。 场景 G = 异步/等待的并行 API 调用。

并行API呼叫

在此处输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM