5-10.【OC】【Block】为什么在 OC 中 weakSelf 几乎是必须的?哪些场景下不用 weak 也不会循环引用?

3 阅读3分钟

在 Objective-C 中,weakSelf 的存在感之所以这么强,本质上是因为 Objective-C 的闭包(Block)是一等公民,且默认执行的是强引用捕获。


1. 为什么 weakSelf 几乎是必须的?

核心原因在于 “双向强引用” 导致的内存僵局。

在典型的开发场景(如 Controller 持有 View,View 持有 Block)中,如果不使用 weakSelf,会形成以下闭环:

  • 对象 A 拥有 Block B(作为属性)。
  • Block B 被拷贝到堆上,为了保证执行时环境有效,它会强引用捕获到的 对象 A
  • 结果: A 持有 B,B 持有 A。引用计数永远无法归零,造成 内存泄漏(Memory Leak)

2. 哪些场景不用 weak 也不会循环引用?

并不是所有 Block 都会导致循环引用。判定标准很简单:是否存在“你持有我,我同时也持有你”的环。 以下场景可以安全使用 self

A. 临时性的静态方法(类方法)

当你调用 UIViewNSURLSession 的类方法时,这些方法内部虽然使用了 Block,但这些 Block 是被“类”或者“系统管理单例”临时持有的,self 并没有持有这个 Block。

  • 例子: [UIView animateWithDuration:animations:^{ [self doSomething]; }]
  • 原理: selfUIView (无引用关系),UIView (类) → Block。动画结束,Block 销毁,环不存在。

B. GCD 异步调用

使用 dispatch_asyncdispatch_after 时。

  • 例子: dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; });
  • 原理: 系统底层队列(Queue)持有了这个 Block,而你的 self 并没有持有队列。虽然 Block 会强引用 self 导致 self 延迟释放(直到任务完成),但任务一旦跑完,Block 就会被销毁,引用释放,不会永久泄漏。

C. “一次性”使用的系统 API

例如 NSNotificationCenter 的监听(如果不把返回的 observer 存为属性)或 enumerateObjectsUsingBlock:

  • 原理: 集合对象(Array/Dict)在执行遍历时临时持有 Block,执行完即释放。

D. Block 作为参数传递,但未被属性记录

如果你只是把 Block 传给一个方法去执行,而那个方法内部并没有将这个 Block 赋值给它的成员变量或属性,那么也不会有环。


3. 黄金准则:如何判断该不该用?

你可以问自己两个问题:

  1. self 有没有持有这个 Block? (即:Block 是不是 self 的属性或成员变量?)
  2. 这个 Block 是否会长期存在?
  • 如果 1 是,2 是 \rightarrow 必须用 weakSelf
  • 如果 1 否,2 否 \rightarrow 可以不用
  • 如果 1 否,2 是(如 GCD 延时 100 秒) \rightarrow 建议用 weakSelf,否则对象会一直活着直到 Block 执行完。

4. 进阶:为什么要搭配 strongSelf

在 Block 内部,我们经常看到:

Objective-C

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) return;
    [strongSelf doSomething];
    [strongSelf doAnotherThing];
};

真实语义: weakSelf 是为了打破环;而内部的 strongSelf 是为了防止在执行过程中(比如代码运行到第一行和第二行之间时),self 突然在另一个线程被释放导致 nil 崩溃或逻辑断层。这被称为 Shadow Copy(影子拷贝)。