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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/48713427/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 09:40:11  来源:igfitidea点击:

How to make async / await in Swift?

iosswiftasynchronousasync-await

提问by cusmar

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?

我该怎么办?

回答by cusmar

Thanks to vadian's comment, I found what I expected, and it's pretty easy. I use DispatchGroup(), group.enter(), group.leave()and group.notify(queue: .main){}.

感谢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
    }
}

回答by brandonscript

(Note: Swift 5 may support awaitas you'd expect it in ES6!)

(注意:Swift 5 可能会await像您期望的那样在 ES6 中提供支持!

What you want to look into is Swift's concept of "closures". These were previously known as "blocks" in Objective-C, or completion handlers.

您要研究的是 Swift 的“闭包”概念。这些以前在 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. For example, this in Swift:

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
}        

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. These might give you a little closer parity with awaitand async.

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

回答by SergPanov

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(
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)
    }
}
) } .flatMap { self.andAnotherAPICall(
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)
) } DispatchQueue.main.async { switch result { case let .success(data): print(data) case let .failure(error): print(error) } } } }

Here is the articledescribing it in details.

这是详细描述它的文章

And you can also use primises with PromiseKit and similar libraries

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

回答by Alex Belozierov

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 时,它不会阻塞线程,而只会挂起协程,因此您也可以在主线程中使用它。

##代码##

回答by Bill

In iOS 13 and up, you can now do this using Combine. Futureis analogous to asyncand the flatMapoperator on publishers (Futureis a publisher) is like await. Here's an example, loosely based on your code:

在 iOS 13 及更高版本中,您现在可以使用组合来执行此操作。Future类似于出版商(是出版商)上asyncflatMap运营商Future就像await。这是一个示例,大致基于您的代码:

##代码##