为什么要把并发和内存放在一起讲
在 Swift 里,并发不是独立模块,它和引用计数(ARC)以及闭包捕获规则天然绑定。你一旦写了 Task {} 或 async let,就等于让对象生命周期跨出当前调用栈。很多“偶发崩溃”其实是 并发 + ARC 组合出现的时序问题。
async/await 的基本心智模型
await是“让出线程,不让出上下文”。Task是结构化并发的最小单位,生命周期由父 Task 管。actor是数据隔离,不是线程。
// 1) 结构化并发:父子任务
func loadProfile() async throws -> Profile {
async let user = fetchUser()
async let stats = fetchStats()
return try await Profile(user: user, stats: stats)
}
// 2) actor 保护共享状态
actor Cache {
private var store: [String: Data] = [:]
func get(_ key: String) -> Data? { store[key] }
func set(_ key: String, _ value: Data) { store[key] = value }
}
ARC 与捕获列表:避免“活太久”与“死太早”
常见坑:
Task捕获self,导致生命周期延长,内存不释放。weak self过早释放,导致任务内访问失败。
推荐实践:
class ProfileViewModel {
private var task: Task<Void, Never>?
func refresh() {
task?.cancel()
task = Task { [weak self] in
guard let self else { return }
let profile = try? await loadProfile()
await MainActor.run {
self.apply(profile)
}
}
}
}
- 短任务用
weak self,长任务考虑 显式取消。 - UI 更新请放到
MainActor。
一个简单的“内存泄漏自检”清单
- 是否在
Task中捕获了self但没有取消? - 是否在
actor里保存了闭包/回调导致强引用? - 是否用
NotificationCenter没有移除观察者?
最后一点建议
先把并发当作结构管理问题,再考虑性能。能把生命周期说清楚的并发,才是好并发。