2-14.【OC】【内存管理】delegate 为什么通常用 weak?

0 阅读2分钟

在 Objective-C 和 Swift 开发中,delegate(代理)属性几乎约定俗成地使用 weak(在 MRC 下用 assign)。这背后的核心原因只有一个:防止循环引用(Retain Cycle)导致的内存泄露。

我们可以从“权力结构”和“物理连接”两个角度来理解:


1. 典型的父子关系(所有权模型)

在大多数场景中,代理模式是从属关系的体现。

  • ViewController (父/持有者) :它创建并持有一个 UITableView(子/被持有者)。这时 ViewController 对 View 有一个 strong 引用。
  • TableView (子/被代理者) :它需要告诉 ViewController “我被点击了”。

如果 TableViewdelegate 也是 strong

  1. ViewController 强引用 TableView。
  2. TableView 强引用 ViewController。

结果: 当 ViewController 试图销毁时,它发现 TableView 还没放手;而 TableView 只有在 ViewController 销毁时才会被销毁。两个对象就像互相锁死的齿轮,引用计数永远不会降到 0,内存永远无法回收。


2. weak 的“单向持有”方案

delegate 声明为 weak,打破了这种双向强引用的闭环:

  • ViewController strong\xrightarrow{strong} TableView
  • TableView weak\xrightarrow{weak} 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 组件、长期存在的父子关系阅后即焚的异步任务、匿名代理