Swift 并发与内存管理:从 async 到 ARC

用最少概念串起 Swift 并发模型与内存安全的核心实践。

阅读时长: 5 分钟

为什么要把并发和内存放在一起讲

在 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 没有移除观察者?

最后一点建议

先把并发当作结构管理问题,再考虑性能。能把生命周期说清楚的并发,才是好并发。

共书写了 11.5k 字 · 共 15 篇文章
Built with Nuxt 3
Theme Stack designed by Jimmy