一句话结论(先给你记忆锚点)
ARC 会成为热点,几乎只在这三种情况下:
- 短生命周期对象在热路径中反复出现
- 值类型被迫逃逸到 heap(boxing)
- 引用被频繁跨边界传递(closure / 协议 / 并发)
如果你看到 swift_retain / swift_release 排到 Time Profiler 前几名,一定不是“ARC 太慢” ,而是代码结构在逼 ARC 工作。
二、ARC 成为热点的典型触发条件(非常具体)
1️⃣ 热路径 + 短命对象(最常见)
结构特征
for item in items {
process(item)
}
看起来无害,但如果 item 或 process 内部涉及:
- class
- closure
- existential container
- escaping value
👉 每一轮循环都是 retain + release
Instruments 特征
-
Time Profiler:
swift_retainswift_release
-
Allocations:
- 生命周期极短
- 分配速率高
关键点
ARC 不怕“多”,怕“频繁 + 短命”
2️⃣ 值类型被 boxing 到 heap(非常隐蔽)
触发场景
let p: P = MyStruct() // struct 被装箱
或者:
func f(_ x: P) { ... } // P 是 protocol existential
- struct → existential container
- container 在 heap
- 每次传递都 retain/release
为什么危险?
- 你以为你在用 struct(值类型)
- 实际在操作 heap object + ARC
Instruments 信号
- swift_allocObject
- swift_retain / release
- witness table 调用
3️⃣ closure 捕获引用(尤其在 map / forEach)
items.map { item in
self.update(item)
}
发生了什么:
- closure 是 heap object
- 捕获
self - 每次调用 retain/release self
高危变体
- closure 在循环中创建
- closure 逃逸(async / stored)
4️⃣ 跨并发边界(Dispatch / Task / Actor)
DispatchQueue.global().async {
self.work()
}
self被 retain- 跨线程 → ARC 需要原子操作
- retain/release 成本更高
特别容易炸的情况
- 大量短任务
- 频繁调度
- TaskGroup / async let 热路径
5️⃣ SwiftUI / Combine 的隐式 ARC 风暴
SwiftUI
- body 频繁计算
- closure + View struct
- state 变化粒度太细
Combine
- map / flatMap / sink
- closure 链式调用
- AnyPublisher(类型擦除)
👉 表现为:
- retain/release 密集
- CPU 不高但帧率掉
三、什么时候 ARC 不会 成为热点?
这是判断的重要反面例子。
✅ 不会成为热点的情况
| 场景 | 原因 |
|---|---|
| 长生命周期对象 | retain/release 被摊销 |
| 单次 retain,多次使用 | amortized |
| struct + 泛型 + inout | 无 ARC |
| final class 内部调用 | retain 可被消除 |
| non-escaping closure | 编译期优化 |
很重要的一点:
ARC 开销不是线性的,而是“边界触发型”
四、ARC 成为热点时的“黄金诊断流程”
Step 1:Time Profiler
看三个名字:
swift_retainswift_releaseswift_allocObject
Step 2:展开调用栈,问三个问题
- 是不是在循环里?
- 是不是 protocol / closure / async?
- 是不是 struct 看起来很“值”,但实际在 heap?
只要三个里中一个,ARC 就有嫌疑。
五、结构级解决方式(不是微调)
🔧 1️⃣ 减少 heap 对象
- 用 struct
- 用泛型
- 避免 existential
🔧 2️⃣ 减少生命周期抖动
// ❌
for x in xs {
let tmp = Obj(x)
use(tmp)
}
// ✅
let pool = ObjPool()
for x in xs {
pool.with(x) { use($0) }
}
🔧 3️⃣ 减少跨边界 retain
- inout
- local copy
- 合并 async
🔧 4️⃣ 用 final / private 帮助 ARC 消除
final class C {
func work() { ... }
}
- 编译器可静态分析
- retain/release 可消除或下沉
六、最终总结(非常重要)
ARC 成为热点,从来不是“引用计数机制不行”
而是:
- 你在热路径中制造了大量短命引用
- 你以为是值,其实是 heap
- 你频繁跨抽象边界
当你在 Instruments 里看到 ARC 热点时,不要去“优化 ARC” ,
而要问:
“我这个 API / 数据流,为什么会制造这么多短命引用?”