在 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. 临时性的静态方法(类方法)
当你调用 UIView 或 NSURLSession 的类方法时,这些方法内部虽然使用了 Block,但这些 Block 是被“类”或者“系统管理单例”临时持有的,self 并没有持有这个 Block。
- 例子:
[UIView animateWithDuration:animations:^{ [self doSomething]; }] - 原理:
self→UIView(无引用关系),UIView(类) →Block。动画结束,Block 销毁,环不存在。
B. GCD 异步调用
使用 dispatch_async 或 dispatch_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. 黄金准则:如何判断该不该用?
你可以问自己两个问题:
self有没有持有这个 Block? (即:Block 是不是self的属性或成员变量?)- 这个 Block 是否会长期存在?
- 如果 1 是,2 是 必须用
weakSelf。 - 如果 1 否,2 否 可以不用。
- 如果 1 否,2 是(如 GCD 延时 100 秒) 建议用
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(影子拷贝)。