Swift 5.5 在 WWDC 2021 中引入了 async/await,随后在 Swift 6 中进一步完善,成为现代 iOS 开发中处理并发的核心工具。它不仅让异步代码更易读写,还彻底改变了我们组织并发任务的方式。
什么是 async?
async
是一个方法修饰符,表示该方法是异步执行的,即不会阻塞当前线程,而是挂起等待结果。
✅ 示例:定义一个 async 方法
swift
func fetchImages() async throws -> [UIImage] {
// 模拟网络请求
let data = try await URLSession.shared.data(from: URL(string: "https://example.com/images")!).0
return try JSONDecoder().decode([UIImage].self, from: data)
}
async
表示异步执行;throws
表示可能抛出错误;- 返回值是
[UIImage]
; - 调用时需要用
await
等待结果。
什么是 await?
await
是调用 async
方法时必须使用的关键字,表示"等待异步结果"。
✅ 示例:使用 await 调用 async 方法
swift
do {
let images = try await fetchImages()
print("成功获取 \(images.count) 张图片")
} catch {
print("获取图片失败:\(error)")
}
- 使用
try await
等待异步结果; - 错误用
catch
捕获; - 代码顺序执行,逻辑清晰。
async/await 如何替代回调地狱?
在 async/await 出现之前,异步操作通常使用回调闭包,这会导致回调地狱(Callback Hell):
❌ 旧写法:嵌套回调
swift
fetchImages { result in
switch result {
case .success(let images):
resizeImages(images) { result in
switch result {
case .success(let resized):
print("处理完成:\(resized.count) 张图片")
case .failure(let error):
print("处理失败:\(error)")
}
}
case .failure(let error):
print("获取失败:\(error)")
}
}
✅ 新写法:线性结构
swift
do {
let images = try await fetchImages()
let resizedImages = try await resizeImages(images)
print("处理完成:\(resizedImages.count) 张图片")
} catch {
print("处理失败:\(error)")
}
- 没有嵌套;
- 顺序清晰;
- 更易于维护和测试。
在非并发环境中调用 async 方法
如果你尝试在同步函数中直接调用 async
方法,会报错:
'async' call in a function that does not support concurrency
✅ 解决方案:使用 Task
swift
final class ContentViewModel: ObservableObject {
@Published var images: [UIImage] = []
func fetchData() {
Task { @MainActor in
do {
self.images = try await fetchImages()
} catch {
print("获取失败:\(error)")
}
}
}
}
Task {}
创建一个新的异步上下文;@MainActor
保证 UI 更新在主线程;- 适用于 SwiftUI 或 UIKit。
如何在旧项目中逐步迁移?
Xcode 提供了三种自动重构方式,帮助你从旧回调方式迁移到 async/await:
✅ 方式一:Convert Function to Async
直接替换旧方法,不保留旧实现:
swift
// 旧
func fetchImages(completion: @escaping (Result<[UIImage], Error>) -> Void)
// 新
func fetchImages() async throws -> [UIImage]
✅ 方式二:Add Async Alternative
保留旧方法,并添加新 async 方法,使用 @available
标记:
swift
@available(*, deprecated, 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] {
// 新实现
}
- 旧方法调用会提示警告;
- 支持逐步迁移;
- 不破坏现有代码。
✅ 方式三:Add Async Wrapper
使用 withCheckedThrowingContinuation
包装旧方法:
swift
func fetchImages() async throws -> [UIImage] {
try await withCheckedThrowingContinuation { continuation in
fetchImages { result in
continuation.resume(with: result)
}
}
}
- 无需改动旧实现;
- 适合第三方库或无法修改的代码。
async/await 会取代 Result 枚举吗?
虽然 async/await 让 Result
枚举看起来不再必要,但它不会立即消失。很多老代码和第三方库仍在使用 Result
,但未来可能会逐步弃用。
迁移建议:先 async,再 Swift 6
- Swift 6 引入了更强的并发安全检查;
- 建议先迁移到 async/await,再升级到 Swift 6;
- 使用
@preconcurrency
和@Sendable
等工具逐步迁移。
总结:async/await 带来的改变
特性 | 回调方式 | async/await |
---|---|---|
可读性 | 差(嵌套) | 好(线性) |
错误处理 | 手动 Result | try/catch |
并发控制 | 手动管理 | 结构化 |
测试难度 | 高 | 低 |
与 SwiftUI 集成 | 复杂 | 自然 |