简体   繁体   English

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

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

I would like to simulate async and await request from Javascript to Swift 4. I searched a lot on how to do it, and I thought I found the answer with DispatchQueue , but I don't understand how it works.我想模拟从 Javascript 到 Swift 的异步和等待请求 4. 我搜索了很多有关如何执行此操作的信息,我认为我找到了DispatchQueue的答案,但我不明白它是如何工作的。

I want to do a simple stuff:我想做一个简单的事情:

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)
}

What should I do?我应该怎么办?

Thanks to vadian 's comment, I found what I expected, and it's pretty easy.感谢vdian的评论,我找到了我所期望的,而且很容易。 I use DispatchGroup() , group.enter() , group.leave() and group.notify(queue: .main){} .我使用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
    }
}

We have to await!我们必须等待!

The Swift Evolution proposal SE-0296 async/await has been accepted after 2 pitches and revision modifications recently on December 24th 2020 . Swift Evolution 提案SE-0296 async/await在最近202012 月 24 日经过 2 次推介和修订修改后已被接受。 This means that we will be able to use the feature in the near future ( Swift 5.5 ).这意味着我们将能够在不久的将来使用该功能( Swift 5.5 )。 The reason for the delay is due to backwards-compatibility issues with Objective-C , see SE-0297 Concurrency Interoperability with Objective-C .延迟的原因是与 Objective-C 的向后兼容性问题,请参阅SE-0297与 Objective-C 的并发互操作性 There are many side-effects and dependencies of introducing such a major language feature, so we can only use the experimental toolchain for now.引入这样一个主要的语言特性有很多副作用和依赖性,所以我们现在只能使用实验性工具链。 Because SE-0296 had 2 revisions, SE-0297 actually got accepted before SE-0296.因为 SE-0296 有 2 个修订版,所以SE-0297实际上在 SE-0296 之前就被接受了。

General Use一般用途

We can define an asynchronous function with the following syntax:我们可以使用以下语法定义异步函数:

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

The idea here is to include the async keyword alongside the return type since the call site will return ( BOOL here) when complete if we use the new await keyword.这里的想法是在返回类型旁边包含async关键字,因为如果我们使用新的await关键字,调用站点将在完成时返回(此处为BOOL

To wait for the function to complete, we can use await :要等待函数完成,我们可以使用await

let result = await raiseHand()

Synchronous/Asynchronous同步/异步

Defining synchronous functions as asynchronous is ONLY forward-compatible - we cannot declare asynchronous functions as synchronous.将同步函数定义为异步函数只是向前兼容的——我们不能将异步函数声明为同步函数。 These rules apply for function variable semantics , and also for closures when passed as parameters or as properties themselves.这些规则适用于函数变量语义,也适用作为参数或属性本身传递的闭包

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

Throwing functions投掷功能

The same consistency constraints are applied to throwing functions with throws in their method signature, and we can use @autoclosures as long as the function itself is async .相同的一致性约束应用于在方法签名中带有throws throwing 函数,只要函数本身是async ,我们就可以使用@autoclosures

We can also use try variants such as try?我们还可以使用try变体,例如try? or try!try! whenever we await a throwing async function, as standard Swift syntax.每当我们等待抛出async函数时,就像标准的 Swift 语法一样。

rethrows unfortunately still needs to go through Proposal Review before it can be incorporated because of radical ABI differences between the async method implementation and the thinner rethrows ABI (Apple wants to delay the integration until the inefficiencies get ironed out with a separate proposal).不幸的是, rethrows在合并之前仍然需要通过提案审查,因为async方法实现和更薄的rethrows ABI 之间存在根本的 ABI 差异(Apple 希望延迟集成,直到通过单独的提案解决低效率问题)。

Networking callbacks网络回调

This is the classic use-case for async/await and is also where you would need to modify your code :这是async/await经典用例,也是您需要修改代码的地方

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

Change to this :改成这样

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(:)`

The asynchronous networking methods such as NSURLSession.dataTask now has asynchronous alternatives for async/await. NSURLSession.dataTask等异步网络方法现在有 async/await 的异步替代方案 However, rather than passing an error in the completion block, the async function will throw an error.但是,异步函数不会在完成块中传递错误而是会抛出错误。 Thus, we have to use try await to enable throwing behaviour.因此,我们必须使用try await来启用抛出行为。 These changes are made possible because of SE-0297 since NSURLSession belongs to Foundation which is still largely Objective-C .由于NSURLSession属于仍然主要是 Objective-C 的Foundation ,因此SE- NSURLSession使这些更改成为可能。

Code impacts代码影响

  • This feature really cleans up a codebase, goodbye Pyramid of Doom 👋!此功能确实清理了代码库,再见末日金字塔👋!

  • As well as cleaning up the codebase, we improve error handling for nested networking callbacks since the error and result are separated.除了清理代码库之外,我们还改进了嵌套网络回调的错误处理,因为错误和结果是分开的。

  • We can use multiple await statements in succession to reduce the dependency on DispatchGroup .我们可以连续使用多个await语句来减少对DispatchGroup的依赖。 👋 to Threading Deadlocks when synchronising DispatchGroup s across different DispatchQueue s. 👋 跨不同DispatchQueue同步DispatchGroup线程死锁

  • Less error-prone because the API is clearer to read.不易出错,因为 API 更易于阅读。 Not considering all exit paths from a completions handler, and conditional branching means subtle bugs can build up that are not caught at compile time.不考虑完成处理程序的所有退出路径条件分支意味着可能会累积在编译时未捕获的细微错误。

  • async / await is not back-deployable to devices running < iOS 15, so we have to add if #available(iOS 15, *) checks where supporting old devices. async / await不能回部署到运行 < iOS 15 的设备,因此我们必须添加if #available(iOS 15, *)检查支持旧设备的位置。 We still need to use GCD for older OS versions.对于较旧的操作系统版本,我们仍然需要使用 GCD。

(Note: Swift 5 may support await as you'd expect it in ES6! ) (注意: Swift 5 可能支持await正如您在 ES6 中所期望的那样!

What you want to look into is Swift's concept of "closures".您要研究的是 Swift 的“闭包”概念。 These were previously known as "blocks" in Objective-C, or completion handlers.这些以前在 Objective-C 中称为“块”或完成处理程序。

Where the similarity in JavaScript and Swift come into play, is that both allow you to pass a "callback" function to another function, and have it execute when the long-running operation is complete. JavaScript 和 Swift 的相似之处在于,两者都允许您将“回调”函数传递给另一个函数,并在长时间运行的操作完成时执行。 For example, this in 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
}        

would look like this in JavaScript:在 JavaScript 中看起来像这样:

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

There's also a few libraries out there, notably a new one by Google that translates closures into promises: https://github.com/google/promises .还有一些库,特别是谷歌的一个新库,它将闭包转换为承诺: https : //github.com/google/promises These might give you a little closer parity with await and async .这些可能会让你更接近awaitasync

You can use semaphores to simulate async/await.您可以使用信号量来模拟 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
 }

And here is example how it works with consecutive API calls:以下是它如何处理连续 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)
                }
            }
        }
    }

Here is the article describing it in details.这是详细描述它的文章

And you can also use promises with PromiseKit and similar libraries您还可以将 Promise 与 PromiseKit 和类似的库一起使用

You can use this framework for Swift coroutines - https://github.com/belozierov/SwiftCoroutine您可以将此框架用于 Swift 协程 - https://github.com/belozierov/SwiftCoroutine

Unlike DispatchSemaphore, when you call await it doesn't block the thread but only suspends coroutine, so you can use it in the main thread as well.与 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)
    }
}

In iOS 13 and up, you can now do this using Combine.在 iOS 13 及更高版本中,您现在可以使用组合来执行此操作。 Future is analogous to async and the flatMap operator on publishers ( Future is a publisher) is like await . Future类似于async并且发布者上的flatMap操作符( Future是一个发布者)就像await Here's an example, loosely based on your code:这是一个示例,大致基于您的代码:

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)

Edit : I went into more detail in this blog post .编辑:我在这篇博文中详细介绍了。

Async/await is now officially supported in Swift. Swift 现在正式支持 Async/await。

It would yield be something like this它会产生像这样的东西

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
}

Use async/ await below like this,像这样在下面使用 async/await,

enum DownloadError: Error {

case statusNotOk
case decoderError

}

Method Call方法调用

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
    
}

Here you can see a basic difference, how the callBack replacing by async/await在这里你可以看到一个基本的区别,callBack 如何被 async/await 替换

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

Basically, scene A and scene B = Call API by the closure.基本上,场景A和场景B=通过闭包调用API。 Scene C and scene D= Calling API by Async/Await.场景 C 和场景 D= 通过 Async/Await 调用 API。 Scene E = Serial API call by nested closure.场景 E = Serial API 通过嵌套闭包调用。 Scene F= Serial API call by Async/Await.场景 F= Serial API 由 Async/Await 调用。 Scene G = Parallel API call by Async/Await.场景 G = 异步/等待的并行 API 调用。

Parallel API call并行API呼叫

在此处输入图像描述

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

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