一句话结论(先立住直觉)
Swift 并发(Task / async-await)下,ARC 的“单位成本”通常更低,但“调用次数”更容易被放大。
GCD 下,ARC 次数少,但每次更贵、更不可控。
所以:
- 少量、粗粒度任务 → Task 更快
- 大量、细粒度任务 → Task 更容易 ARC 爆炸
一、ARC 成本模型的根本不同
GCD:线程 + block
DispatchQueue.global().async {
self.work()
}
ARC 行为
- block 是 heap object
- 捕获 self → retain
- 跨线程 → retain/release 是 原子操作
- 生命周期 = block 执行完
👉 ARC 次数少,但每次都很重
Swift 并发:Task + continuation
Task {
await self.work()
}
ARC 行为
- Task 是结构体 + runtime object
- 捕获值 → 被复制进 Task context
- suspend / resume → context retain/release
- continuation 是 heap object
👉 ARC 更“细碎”,但单次更轻
二、关键差异 ①:ARC 调用“位置”不同
GCD
-
ARC 主要发生在:
- block 创建
- block 执行结束
retain self
↓
执行 block
↓
release self
生命周期清晰、边界明显
Swift 并发
ARC 分布在:
- Task 创建
- await suspend
- await resume
- continuation 链接
- Task 完成
retain
suspend → retain
resume → release
await → retain
👉 一次 async 函数,可能触发多次 retain/release
三、关键差异 ②:值捕获 vs 引用捕获
GCD
-
捕获的引用:
- 明确 retain
- 生命周期 = block
Swift 并发
- 值类型会被复制
- 引用类型会被 retain
- 有时 struct 仍会被 box(context)
Task {
let x = bigStruct // copy
await f(x)
}
- 大 struct → copy 成本
- 或被提升到 heap → ARC
👉 不是“用 struct 就没 ARC”
四、关键差异 ③:优化空间不同
GCD 的限制
-
block 是 escaping closure
-
编译器假设:
- 任意线程
- 任意时间
-
很多优化直接放弃
Swift 并发的优势
-
语言级语义
-
编译器知道:
- suspend 点
- isolation
- actor 边界
👉 在理想结构下,Task 的 ARC 更容易被合并 / 消除
但前提是——结构得对
五、Task 中 ARC 特别容易“炸”的场景
🔥 1️⃣ 细粒度 Task
for item in items {
Task {
await process(item)
}
}
-
每个 Task:
- context
- continuation
- ARC
👉 比 GCD 更容易炸
🔥 2️⃣ Task + 捕获 self
Task {
await self.update()
}
- self 被 capture
- suspend 时 self 必须存活
- retain 跨多个 await
🔥 3️⃣ actor hopping
await actorA.work()
await actorB.work()
-
每次 hop:
- retain context
- retain self
- message send
六、GCD 反而更“稳”的场景
✅ 长任务 + 少 await
DispatchQueue.global().async {
heavyWork() // 50ms
}
- ARC:1 次 retain + release
- 无 suspend
✅ C / ObjC 桥接重的代码
- async/await 反而增加 context
七、如何在 Swift 并发下“驯服 ARC”
🧠 1️⃣ 控制 Task 粒度(最重要)
Task 是重量级语义,不是 map 的替代品
// ❌
items.map {
Task { await process($0) }
}
// ✅
await withTaskGroup(of: Void.self) { group in
for chunk in chunks {
group.addTask {
for item in chunk {
await process(item)
}
}
}
}
🧠 2️⃣ 避免 Task 捕获 self
let service = self.service
Task {
await service.run()
}
🧧 3️⃣ 减少 suspend 点
// ❌ 多个 await
await a()
await b()
// ✅ 合并
let (x, y) = await (a(), b())
🧠 4️⃣ 用 nonisolated / @inlinable 辅助优化
让编译器更容易消除 ARC
八、Instruments 中的典型信号
Swift 并发 ARC 问题
swift_task_allocswift_task_deallocswift_retainswift_releaseswift_continuation_*
九、最终总结(记住这张对比表)
| 维度 | GCD | Swift 并发 |
|---|---|---|
| ARC 次数 | 少 | 多 |
| 单次 ARC 成本 | 高 | 低 |
| 优化空间 | 小 | 大 |
| 易踩坑点 | 跨线程 retain | 细粒度 Task |
| 热路径适配 | 中 | 需设计 |
最重要的一句话
Swift 并发不是“更便宜的 GCD”,而是“更聪明但更啰嗦的 runtime”。
如果你把它当 GCD 用,ARC 一定会惩罚你。