在 Objective-C 和 Swift 开发中,delegate(代理)属性几乎约定俗成地使用 weak(在 MRC 下用 assign)。这背后的核心原因只有一个:防止循环引用(Retain Cycle)导致的内存泄露。
我们可以从“权力结构”和“物理连接”两个角度来理解:
1. 典型的父子关系(所有权模型)
在大多数场景中,代理模式是从属关系的体现。
- ViewController (父/持有者) :它创建并持有一个
UITableView(子/被持有者)。这时 ViewController 对 View 有一个strong引用。 - TableView (子/被代理者) :它需要告诉 ViewController “我被点击了”。
如果 TableView 的 delegate 也是 strong:
- ViewController 强引用 TableView。
- TableView 强引用 ViewController。
结果: 当 ViewController 试图销毁时,它发现 TableView 还没放手;而 TableView 只有在 ViewController 销毁时才会被销毁。两个对象就像互相锁死的齿轮,引用计数永远不会降到 0,内存永远无法回收。
2. weak 的“单向持有”方案
将 delegate 声明为 weak,打破了这种双向强引用的闭环:
- ViewController TableView
- TableView ViewController
在这种结构下,只有一条强引用链。当用户返回上一个界面,ViewController 的局部指针失效,其引用计数归零并销毁。随后,它持有的 TableView 也会因为失去唯一的强引用而随之销毁。
3. 安全性:防止野指针崩溃
为什么不用 __unsafe_unretained(或 MRC 下的 assign)?
- 危险:如果使用
assign,当 ViewController 销毁后,TableView 里的delegate指针依然指向那个已经回收的内存地址。 - 后果:如果此时 TableView 触发了一个事件尝试调用
[delegate someMethod],程序会直接 EXC_BAD_ACCESS 崩溃。 - weak 的优势:正如前面讨论的,
weak指针在 ViewController 销毁时会自动被 Runtime 置为nil。向nil发送消息在 Objective-C 中是安全的,不会产生任何副作用。
4. 例外情况:什么时候可以用 strong?
虽然 99% 的代理是 weak,但有些特殊设计会使用 strong,这被称为 “临时强引用代理” 。
- 场景:异步任务执行器(如某个封装好的网络请求类或动画类)。
- 逻辑:任务开始时,执行器强持有代理,确保任务执行期间代理对象不消失。任务结束(回调完成)后,执行器主动将
delegate置为nil。 - 注意:这种模式必须有明确的“任务结束”标志,否则会造成永久泄露。
总结
| 维度 | 使用 weak (推荐) | 使用 strong (极少见) |
|---|---|---|
| 内存关系 | 只有一方负责另一方的生命 | 双方互相依赖生命 |
| 风险 | 无(自动变 nil) | 极易导致死锁/泄露 |
| 主要用途 | UI 组件、长期存在的父子关系 | 阅后即焚的异步任务、匿名代理 |