Swift 5.5 终于为开发者带来了async,await,actor这些便捷的异步语法。为异步编程开发提供简洁有力的开发范式,并提供精妙的设计使得在开发多线程达到尽可能的高效,同时规避各种多线程开发中常见的错误和异常。
Swift Concurrency框架可以分为以下几个部分
Async/await
swift函数调用可以分为同步和异步调用。同步函数在执行完立即得到返回结果,而其中异步调用则需要配合await等待。一个典型的async函数如下:
func fetchImages() async throws -> [UIImage] {
// .. perform data request
}
fetchImages 函数执行一个异步动作,并且有可能抛出异常,这个函数要么返回结果Image数组,要么抛出异常。这样的一个任务也可以通过编写异步回调函数来解决:
func fetchImages(completion: ([UIImage]?, Error?) -> Void) {
// .. perform data request
}
使用异步回调函数有个令人头疼的问题就是冗长的回调嵌套,在多个异步调用流程我们不得不对每个步骤的异步回调进行各种异常case处理,而通常处理的逻辑是类似的
// 1. Call the method
fetchImages { result in
// 3. The asynchronous method returns
switch result {
case .success(let images):
print("Fetched \(images.count) images.")
// 4. Call the resize method
resizeImages(images) { result in
// 6. Resize method returns
switch result {
case .success(let images):
updateImageList(images)
case .failure(let error):
updateFetchImageFailed()
}
}
// 5. Fetch images method returns
case .failure(let error):
print("Fetching images failed with error \(error)")
updateFetchImageFailed()
}
}
// 2. The calling method exits
通过async-await的语法,我们可以简化上述逻辑为:
do {
// 1. Call the method
let images = try await fetchImages()
// 2. Fetch images method returns
// 3. Call the resize method
let resizedImages = try await resizeImages(images)
// 4. Resize method returns
updateImageList(images)
} catch {
updateFetchImageFailed()
}
// 5. The calling method exits
通过async-await可以将执行逻辑线性化,和同步代码调用类似,非常便捷我们梳理代码执行逻辑,也减少了代码编写难度。使用async-wait还有以下好处:
- 避免在回调处理的各种case中遗漏数据结果返回或者调用完成
- 避免未正确处理weak引用导致循环引用内存泄露
- 不能快速返回,而通过await的try catch语句可以快速进入异常case处理。
初次使用async-await方法可能遇到编译错误:
在非async方法使用await可能导致编译错误
在一个同步方法中调用await方法会触发:“‘async’ call in a function that does not support concurrency.”错误,通过给方法添加async修饰符可以规避这种错误:
func fetchData() async {
do {
try await fetchImages()
} catch {
// .. handle error
}
}
不过针对一些不需要传递异步返回值的场景,使用async修饰怪怪的,也改变了方法的本质含义。我们可以通过使用Task.init
来解决这个问题。
final class ContentViewModel: ObservableObject {
@Published var images: [UIImage] = []
func fetchData() {
Task.init {
do {
self.images = try await fetchImages()
} catch {
// .. handle error
}
}
}
}
为了方便把现有的回调方法改造成async-await风格方法,Xcode还提供了代码重构工具。选择异步回调的方法在右键菜单可以弹出重构为async方法。
重构现有代码为async调用
struct ImageFetcher {
func fetchImages(completion: @escaping (Result<[UIImage], Error>) -> Void) {
// .. perform data request
}
}
选择Convert Function to Async
,效果如下:
struct ImageFetcher {
func fetchImages() async throws -> [UIImage] {
// .. perform data request
}
}
选择Add Async Alternative,会在原有方法生成@available(*, renamed: "XXXX")签注,同时保留两种调用风格的函数
struct ImageFetcher {
@available(*, renamed: "fetchImages()")
func fetchImages(completion: @escaping (Result<[UIImage], Error>) -> Void) {
Task {
do {
let result = try await fetchImages()
completion(.success(result))
} catch {
completion(.failure(error))
}
}
}
func fetchImages() async throws -> [UIImage] {
// .. perform data request
}
}
使用Add Async Wrapper
struct ImageFetcher {
@available(*, renamed: "fetchImages()")
func fetchImages(completion: @escaping (Result<[UIImage], Error>) -> Void) {
// .. perform data request
}
func fetchImages() async throws -> [UIImage] {
return try await withCheckedThrowingContinuation { continuation in
fetchImages() { result in
continuation.resume(with: result)
}
}
}
}
网友评论