[英]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在最近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
关键字,调用站点将在完成时返回(此处为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 。 这些可能会让你更接近await
和async
。
您可以使用信号量来模拟 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
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.