一句话结论(先给你直觉)
性能顺序(单次访问成本)通常是:
强引用 < unowned < weak
但:
- 强引用:有 retain/release
- unowned:无 retain,但有校验
- weak:无 retain,但有 runtime 表查询 + 原子操作
所以在热路径里,weak 往往是最慢的。
一、三种引用的真实成本模型
1️⃣ 强引用(strong)
做了什么
- retain / release
- 多线程下是原子操作
成本特征
- 成本可被 合并、下沉、消除
- 编译器最容易优化
Time Profiler 中看到
swift_retainswift_release
什么时候快?
- 长生命周期
- 非 escaping
- final / private 场景
💡 强引用的“慢”,其实是最可控、最可优化的
2️⃣ unowned(非零成本,但很轻)
做了什么
- 不 retain
- 每次访问做一次有效性检查(trap 保障)
成本模型
- 指针 load
- 轻量校验
- 无哈希表、无 atomic retain
特点
- 比 strong 少 ARC
- 比 weak 少 runtime 查表
风险
- 对象已释放 → crash(不是 nil)
性能上,unowned 是 “接近裸指针”的安全版本
3️⃣ weak(最贵,但最安全)
真正发生了什么?
weak 不是一个简单的可选引用,它涉及:
- 全局 weak reference table
- 对象 deinit 时,表中所有 weak 自动置 nil
- 多线程安全(atomic)
每次访问 weak:
- 通过指针查 weak table
- 原子 load
- nil-check
- 返回 Optional
👉 这是 runtime 行为,不是编译期行为
二、为什么 weak 在很多场景下反而更慢?
🔥 场景 1:热路径中的 weak 访问
for _ in 0..<1_000_000 {
self?.work()
}
每一轮:
- weak table lookup
- atomic load
- Optional unwrap
👉 比一次 retain + release 还贵
🔥 场景 2:频繁解包 weak self
{ [weak self] in
guard let self else { return }
self.work()
}
表面上:
- 只 unwrap 一次
实际上:
- closure invoke
- weak load
- guard 分支
在 tight loop 中仍然明显
🔥 场景 3:跨线程 + weak
- weak 本身就需要同步
- 跨线程访问 → cache miss + fence
👉 成本放大
🔥 场景 4:weak 阻止编译器优化
-
weak 表示生命周期不可预测
-
编译器:
- 不能内联
- 不能消除访问
- 不能 hoist
三、什么时候 strong 反而比 weak 快?
✅ 情况 1:生命周期明确
func run(_ obj: Obj) {
for _ in 0..<1000 {
obj.work()
}
}
- obj 被 retain 一次
- 循环内部无 ARC
✅ 情况 2:用 strong capture + 手动断环
class A {
func setup() {
let handler = { [self] in
self.work()
}
self.handler = handler
}
deinit {
handler = nil
}
}
- 性能好
- 生命周期可控
四、unowned 的真实定位(常被误解)
什么时候 unowned 是最优解?
- 生命周期严格受控
- 对象 保证 存活
- 热路径
- 非并发
性能排序(典型)
裸指针 < unowned < strong < weak
⚠️ 但一旦你不 100% 确定生命周期,unowned 是“性能型炸弹”
五、选择指南(工程级)
性能敏感路径(UI diff / layout / parser)
| 场景 | 推荐 |
|---|---|
| 生命周期明确 | strong / unowned |
| 热循环 | strong(外部 retain) |
| 高频 closure | 解构字段,不捕获 self |
| SwiftUI body | 少用 weak |
非性能敏感路径(回调 / async / delegate)
| 场景 | 推荐 |
|---|---|
| delegate | weak |
| async 回调 | weak |
| observer | weak |
六、一个非常重要但少被提及的点
weak 是一种“同步原语”,不是语言糖
你用 weak 的那一刻,就在告诉 runtime:
“这个引用可能在任何时刻被释放,我需要绝对安全。”
安全是有成本的,而且这个成本是每次访问都要付。
七、最终总结(记住这个)
weak 是最安全的,也是最慢的。
strong 是最容易优化的。
unowned 是性能与风险的中间态。
所以在性能敏感代码里,我的真实决策顺序是:
- 能不用引用 → struct
- 能用 strong 且可控 → strong
- 生命周期严格 → unowned
- 其他 → weak