我来自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有什么方法可以做到这一点?
谢谢。
Xcode 13中即将推出的Swift 5.5(目前仍处于测试版(使用了非常相似的async
-await
模式。请参阅Swift编程语言:并发。
在此期间,令人遗憾的是,还有数量惊人的替代方案。例如,有各种各样的第三方承诺/未来框架。或者还有声明性的Combine框架,它是在几年前随着SwiftUI的非命令式模式的出现而推出的。
话虽如此,您在Swift代码中看到的最常见的模式是使用转义"闭包",这些"闭包"实际上是作为参数传递给函数的代码单元,函数在异步任务完成时调用这些代码单元。在该模式中,您不使用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, execution will continue here, and the above closure will
// be called later, 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
}
这取决于你是否坚持Swift 5.5的测试版,使用非常熟悉的async
-await
模式,使用第三方未来/承诺库,使用Combine,还是使用传统的基于闭包的模式,如上所示。
至少,我建议你熟悉后一种模式,因为它是Swift目前的主要技术。但请放心,熟悉的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)
目前有一个最接近async/await的伟大框架,它是SwiftCoroutinehttps://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)
}
}
在等待来自Apple 的swift 5.5和官方异步/等待时
您可以查看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队列、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
}
}
}