问题描述
我来自 JS,正在学习 Swift 以构建应用的 iOS 原生版本。
在 JS 中,我一直使用以下模式:
...
async function doAyncFunction(item) {
try {
// do async call to fetch data using item
return Promise.resolve(data);
} catch (error) {
return Promise.reject(error);
}
}
const promises = items.map((item) => doAyncFunction(item));
const results = await Promise.all(promises);
...
我已经开始研究 PromiseKit,但我想知道 Swift 的做法是什么?
谢谢。
解决方法
目前有一个很棒的框架,最接近 async/await,它是 SwiftCoroutine https://github.com/belozierov/SwiftCoroutine(比 promiseKit 好多了,我测试了 2..)
Swift 协程与您的示例:
func doFutureFunction() -> CoFuture<Int> {
CoFuture { promise in
myRequest { error,data in
if let error = error {
promise(.failure(error))
} else {
promise(.success(data))
}
}
}
}
let futures = items.map { item in doFutureFunction(item) } // [CoFuture<Int>]
DispatchQueue.main.startCoroutine {
let results = promises.compactMap { try? $0.await() } // [Int]
}
相当于
consts value = await future.value
consts value1 = await future.value
consts value2 = await future.value
console.log("Value " + value + ",value1 " + value1 + ",value2 " + value2)
是
DispatchQueue.main.startCoroutine {
do {
let value = try future.await()
let value1 = try future.await()
let value2 = try future.await()
print("Value \(value),value1 \(value1),value2 \(value2)")
} catch {
print(error.localizedDescription)
}
}
在等待 Swift 5.5 和官方的 async/await 时
,使用上述内置 Combine
框架,您有多种选择。您可能想要的是 Publishers.Merge
:
let publishers = ... // multiple functions that implement the Publisher protocol
let combined = Publishers.MergeMany(publishers)
MergeMany
的替代方案是 Merge
、Merge3
、Merge4
至 Merge8
,当发布者数量已设定时。如果输出的数量是可变的,请使用 MergeMany
。
其他选项包括发布商自己的merge
:
let publisher1 = ...
let publisher2 = ...
publisher1.merge(publisher2)
CombineLatest
或者,在发布者立即完成的情况下,Zip
可用于在一切完成后接收元组:
let publisher1 = ...
let publisher2 = ...
Publishers.CombineLatest(publisher1,publisher2)
,
即将在 Xcode 13 中发布的 Swift 5.5(此时仍处于测试阶段)使用了非常相似的 async
-await
模式。见The Swift Programming Language: Concurrency。
在此期间,不幸的是,有大量令人眼花缭乱的替代方案。例如,有多种第三方承诺/未来框架。或者还有声明式 Combine 框架,它是在几年后随着 SwiftUI 的非命令式模式的出现而推出的。
说了这么多,你会在 Swift 代码中看到的最常见的模式是使用转义“closures”,它是作为参数传递给函数的有效代码单元,并且该函数在异步任务完成时调用。在该模式中,您不需要 await
,而只是指定异步任务完成时您想要做什么。例如,在这个函数中,它有一个名为 completion
的参数,它是一个在异步任务完成时调用的闭包:
func fetch(using value: Int,completion: @escaping (Result<Foo,Error>) -> Void) {
let url = …
let task = URLSession.shared.dataTask(with: url) { data,response,error in
// handle errors,if any,e.g.:
if let error == error else {
completion(.failure(error))
return
}
// parse data into `Foo` here,and when done,call the `completion closure:
…
completion(.success(foo))
}
task.resume()
}
然后你会这样称呼它:
fetch(using: 42,completion: { result in
// this is called when the fetch finishes
switch result {
case .failure(let error): // do something with `error`
case .success(let foo): // do something with `foo`
}
})
// note,execution will continue here,and the above closure will
// be called later,so do not try to use `foo` here
或者,使用更简洁的“尾随闭包”语法:
fetch(using: 42) { result in
// this is called when the fetch finishes
switch result {
case .failure(let error): // do something with `error`
case .success(let foo): // do something with `foo`
}
}
// note,so do not try to use `foo` here
如果您想在完成一系列调用时收到通知,可以使用 DispatchGroup
,例如
let group = DispatchGroup()
for value in values {
group.enter()
fetch(using: value) { result in
// do something with result
group.leave()
}
}
group.notify(queue: .main) {
// this is called when every `enter` call is matched up with a `leave` Call
}
是否坚持使用非常熟悉的 async
-await
模式的 Swift 5.5 测试版、使用第三方 future/promise 库、使用 Combine 或使用传统的基于闭包的模式,如上所示。
至少,我建议您熟悉后一种模式,因为它是目前 Swift 中的主要技术。但是请放心,熟悉的 async
-await
模式即将推出,因此如果您愿意等待它完成测试过程(或加入该测试过程),请查看.
您可以查看 PromiseQ 这是 Swift 的 javascript 风格承诺。它实现了所有 javascript 的 Promise 功能:resolve/reject
、then
、finally
、fetch
等,并附加了一些附加功能:suspend/resume
、cancel
、 retry
、timeout
等
它还支持 all
、race
、any
例如:
// Fetch avatars of first 30 GitHub users.
struct User : Codable {
let login: String
let avatar_url: String
}
async {
let response = try fetch("https://api.github.com/users").await()
guard response.ok else {
throw response.statusCodeDescription
}
guard let data = response.data else {
throw "No data"
}
let users = try JSONDecoder().decode([User].self,from: data)
let images =
try Promise.all(
users
.map { $0.avatar_url }
.map { fetch($0) }
).await()
.compactMap { $0.data }
.compactMap { UIImage(data: $0) }
async(.main) {
print(images.count)
}
}
.catch { error in
print(error.localizedDescription)
}
Swift 的并发性,例如 Dispatch queues、Combine 和最新的 async\await (Swift 5.5) 与 javascript Promises 不同,您找不到以前使用的许多方便的方法。
,我在这里用一个解决方案来回答自己,使用 PromiseKit,以防它可能对某人有所帮助。
以下显然不是一个完整的实现,但它展示了如何实现该模式。
func doManyAsyncRequests(userIds: [String],accessToken: String) -> Promise<Void> {
Promise { seal in
let promises = spotifyUserIds.map {
doSingleAsyncRequest(userId: $0.id,accessToken: accessToken) // this function returns a promise
}
when(fulfilled: promises).done { results in
print("Results: \(results)")
// process results
}.catch { error in
print("\(error)")
// handle error
}
}
}