背景介绍
在 async 关键字被引入之前,我们常用回调和 delegate 方法来处理和响应异步事件。当我们希望保留原有函数(或者原有函数无法被改写,比如在使用第三方库时),创建一个异步函数原有函数的异步函数版本时,就可以用 continuation 来进行改写。
所谓的 continuation(续体)指的是一个函数被分割后的一部分拥有独立存储空间的执行单元。它是我们构建一个异步函数的桥梁。
改写方法
Swift 提供了以下四种全局函数用以转换成异步函数:
func withUnsafeContinuation<T>
func withUnsafeThrowingContinuation<T>
func withCheckedContinuation<T>
func withCheckedThrowingContinuation<T>
举个例子,假如我们想把下面这个函数转化为异步函数:
func IOoperation(completion: @escaping (String) -> Void) {
let queue = DispatchQueue(label: "IOQueue.myApp")
// 模拟耗时操作
queue.asyncAfter(deadline: .now() + 3.0) {
completion("Hello")
}
}
IOoperation
是一个典型的使用回调响应异步事件的函数,这样的函数可能会遇到回调地狱、错误调用等问题。要借助 continuation 把它改写成异步函数,只需这样:func IOoperation() async -> String {
await withUnsafeContinuation { continuation in
IOoperation { str in
continuation.resume(returning: str)
}
}
}
使用方法从:
IOoperation { str in
print(str)
}
变成了:
let t = Task {
let IOStr = await IOoperation()
print(IOStr)
}
带有
Throwing
的 with…Continuation
函数代表函数可能会抛出异常值,如函数:func IOoperationWithError(completion: @escaping (String?, Error?) -> Void) {
let queue = DispatchQueue(label: "IOoperationWithErrorQueue")
queue.asyncAfter(deadline: .now() + 3.0) {
completion("Hi", nil)
}
}
用
withUnsafeThrowingContinuation
改写为:func IOoperationWithError() async throws -> String? {
try await withUnsafeThrowingContinuation{ continuation in
IOoperationWithError { (Str, Err) in
if let Err {
continuation.resume(throwing: Err)
} else if let Str {
continuation.resume(returning: Str)
} else {
assertionFailure("Both parameters are nil")
}
}
}
}
在上面的例子中,可以看到我们对于函数的返回值或者 Error 都使用了
resume()
方法来进行捕获。当我们在一个异步函数中调用
with…Continuation
函数时,这个异步函数将会被暂停, resume()
方法会在之前的暂停点上恢复这个异步函数(即回到原线程上继续执行),并捕获它的返回值或者 Error。这就是我们在异步函数和同步函数之间进行交互的机制。Unsafe 与 Checked 的区别
在官方文档中,有一条重要提示:您程序的每个执行路径上必须调用且仅调用一次
resume()
方法。这是因为,如果从不调用
resume()
方法,任务将永远处于挂起状态,从而可能泄露与之关联的资源;多次调用 resume()
方法,也是未定义的行为,同样可能出现内存泄露等错误导致程序崩溃。带有
Checked
的 with…Continuation
函数,在使用时编译器会保证我们调用且仅调用一次 resume()
方法,而带有 Unsafe
的 with…Continuation
函数,编译器将不作检查,需要我们自己确保正确地调用 resume()
。但是,前者依赖于运行时检查,在对性能要求特别高的场景下,
UnsafeContinuation
可以获得更小的性能开销。总而言之,无论使用何种 continuation,都必须小心对待 resume()
方法的使用。
Loading Comments...