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

cus*_*mar 12 asynchronous ios async-await 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)
}
Run Code Online (Sandbox Code Playgroud)

我该怎么办?

cus*_*mar 22

感谢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
    }
}
Run Code Online (Sandbox Code Playgroud)


Pra*_*tti 7

我们必须等待!

斯威夫特演进建议 SE-0296异步/ AWAIT已经经过2个音高和修改的修改被接受,最近在2020年12月24日。这意味着我们将能够在不久的将来使用该功能(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
}
Run Code Online (Sandbox Code Playgroud)

这里的想法是async在返回类型旁边包含关键字,因为如果我们使用 new关键字,调用站点BOOL在完成时返回(此处)await

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

let result = await raiseHand()
Run Code Online (Sandbox Code Playgroud)

同步/异步

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

var syncNonThrowing: () -> Void
var asyncNonThrowing: () async -> Void
...
asyncNonThrowing = syncNonThrowing // This is OK.
Run Code Online (Sandbox Code Playgroud)

投掷功能

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

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

rethrows不幸的是,由于方法实现和更薄的ABI之间存在根本的 ABI 差异(Apple 希望延迟集成,直到通过单独的提案解决低效率问题),在合并之前仍然需要通过提案审查asyncrethrows

网络回调

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

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

更改为这个

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(:)`
Run Code Online (Sandbox Code Playgroud)

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

代码影响

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

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

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

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

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


bra*_*ipt 6

(注意: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
}        
Run Code Online (Sandbox Code Playgroud)

在 JavaScript 中看起来像这样:

var longRunningOp = function (searchString, callback) {
    // call the callback
    callback(err, result)
}
longRunningOp(searchString, function(err, result) {
    // Do something with the result
})
Run Code Online (Sandbox Code Playgroud)

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


Ser*_*nov 5

您可以使用信号量来模拟异步/等待。

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
 }
Run Code Online (Sandbox Code Playgroud)

以下是它如何处理连续 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)
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是详细描述它的文章。

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


Ale*_*rov 5

您可以将此框架用于 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)
    }
}
Run Code Online (Sandbox Code Playgroud)


Bil*_*ill 5

在 iOS 13 及更高版本中,您现在可以使用组合来执行此操作。Future类似于出版商(是出版商)上asyncflatMap运营商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)
Run Code Online (Sandbox Code Playgroud)

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