Swift 5.5 引入的 async/await 彻底改变了 iOS 开发者的异步编程方式。从最初的回调地狱,到 GCD 的.DispatchQueue,再到今天的 Structured Concurrency,我们终于有了更安全、更易读的并发工具。
但新特性也带来了新的陷阱。本文分享我们在多个项目中使用 Swift Concurrency 的实战经验。
async/await 基础
如果你还没用过 async/await,先看个简单示例:
// 老方式:Completion Handler
func fetchUser(id: String, completion: (Result<User, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
// 处理结果
}.resume()
}
// 新方式:async/await
func fetchUser(id: String) async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
代码从"金字塔"变成线性结构,可读性大幅提升。
Task 与结构化并发
Task是 Swift Concurrency 的基本单位:
Task {
let user = try await fetchUser(id: "123")
await MainActor.run {
self.userNameLabel.stringValue = user.name
}
}
关键原则:
- Task 会自动继承当前上下文(包括 actor 隔离)
- 用
Task.cancel()取消任务,配合Task.checkCancellation()检查 - 父 Task 取消时,子 Task 会自动取消(结构化并发)
Actor 隔离
Actor 用于保护可变状态,避免数据竞争:
actor UserCache {
private var cache: [String: User] = [:]
func get(_ id: String) -> User? { cache[id] }
func set(_ user: User) { cache[user.id] = user }
}
访问 actor 的属性自动需要 await:
let user = await cache.get("123")
MainActor
UI 操作必须在主线程:
@MainActor
class ViewModel {
func updateUI() {
// 自动在主线程执行
}
}
// 或者在方法级别标注
@MainActor
func updateLabel() { ... }
实战场景
1. 并行请求
需要同时请求多个独立数据时,用 async let:
func loadDashboard() async throws -> Dashboard {
async let user = fetchUser()
async let posts = fetchPosts()
async let notifications = fetchNotifications()
return try await Dashboard(
user: user,
posts: posts,
notifications: notifications
)
}
比串行执行快得多。
2. 超时处理
用 withTimeout 避免无限等待:
try await withTimeout(after: 5) {
try await fetchUser(id: "123")
} catch {
// 处理超时
}
3. 重试逻辑
网络请求失败时自动重试:
func fetchWithRetry<T>(
operation: () async throws -> T,
maxRetries: Int = 3
) async throws -> T {
var lastError: Error?
for attempt in 0..<maxRetries {
do {
return try await operation()
} catch {
lastError = error
try await Task.sleep(nanoseconds: 1_000_000_000 * (attempt + 1))
}
}
throw lastError!
}
常见陷阱
1. 忘记处理取消
长时间运行的任务应该检查取消:
func processData(_ items: [Item]) async throws {
for item in items {
try Task.checkCancellation() // 检查是否被取消
try await processOne(item)
}
}
2. 死锁
在 Actor 内部调用需要 await 的方法可能导致死锁:
actor MyActor {
func doSomething() async {
// 错误:在 actor 内部等待自己
await self.doSomethingElse()
}
}
3. 过度使用 MainActor
不是所有 ViewModel 方法都需要 @MainActor,纯数据处理不需要:
class ViewModel {
// 不需要 MainActor
func transformData(_ input: Data) -> Output { ... }
// 需要 MainActor(更新 UI 状态)
@MainActor
func updateStatus(_ status: String) { ... }
}
迁移建议
如果项目还在用 completion handler:
- 先从新代码开始用 async/await
- 用
Task包装老代码 - 逐步重构老代码,优先重构测试代码
- 最后处理复杂的依赖场景
总结
Swift Concurrency 是 iOS 开发的重大进步:
- 用 async/await 替代 completion handler
- 用 Actor 保护可变状态
- 用
async let实现并行请求 - 始终考虑取消和超时处理
正确使用这些工具,代码会更安全、更易读、更高效。