本文介绍 weak(弱引用) 的语义、在运行时中的实现思路(SideTable/weak_table)、循环引用 的成因与破除方式,以及 block、delegate 等场景下的注意点。ARC 基础见 04-ARC详解。Block 的三种类型、copy 与捕获变量见 10-Block内存管理。
一、weak 的语义
1.1 定义
- weak:不增加对象的引用计数,不拥有对象;当对象被释放时,所有指向它的 weak 指针会被自动置为 nil,避免野指针。
- 与 strong 对比:strong 持有对象(rc+1),strong 不释放则对象不 dealloc;weak 不持有,对象可被其他引用释放,释放后 weak 自动置 nil。
1.2 使用场景
| 场景 | 说明 |
|---|
| 打破循环引用 | A → B → A,将其中一条边改为 weak,避免双方都无法释放 |
| 非拥有关系 | delegate、dataSource 等,通常用 weak,由外部持有生命周期 |
| block 内引用 self | 使用 [weak self] 避免 self → block → self 循环 |
二、循环引用(Retain Cycle)
2.1 成因
- 循环引用:对象 A 强引用 B,B 又强引用 A(或经过多条边回到 A),形成环;双方引用计数都不为 0,永远无法 dealloc,造成泄漏。
2.2 常见情形与破除
| 情形 | 破除方式 |
|---|
| 两个对象互相 strong | 一方改为 weak(如 child 对 parent 用 weak) |
| self → block → self | block 内用 [weak self],必要时内部再 strong 一次避免提前释放 |
| delegate 双方都 strong | 通常 delegate 属性声明为 weak,由外部持有 |
| Timer 强引用 target | VC 强引用 timer,timer 强引用 target(即 VC)→ 循环;用 NSProxy 弱引用 VC 作为 timer 的 target,或 iOS 10+ 用 block 版 API |
2.3 Block 中 weak self 示例(Objective-C)
__weak typeof(self) wself = self;
self.block = ^{
__strong typeof(wself) sself = wself;
if (!sself) return;
[sself doSomething];
};
三、NSProxy 与 Weak、Timer 管理
3.1 NSTimer 的循环引用问题
- NSTimer 会强引用其 target;若 target 是 VC(或任意对象 A),且 A 又强引用了该 timer(如
self.timer),则形成 A → timer → target(A) 的循环,A 与 timer 都不会释放。
- 仅在 A 里用
__weak self 给 timer 的 target 传参无效:timer 内部保存的是传入的 target 指针并对其强引用,不会因为调用方用 weak 而改为弱引用。
3.2 用 NSProxy 打破 Timer 循环引用
- 思路:让 timer 的 target 不是一个强引用 self 的对象,而是一个中间对象;该中间对象对 self 只持 weak,并把 timer 的回调转发给 self。这样引用关系为:VC → timer → proxy(弱引用 VC),VC 释放时 proxy 的 weak 置 nil,proxy 可随之释放;timer 需在 VC 的 dealloc 里 invalidate,或由 proxy 在转发时发现 target 为 nil 时 invalidate(视实现而定)。
- NSProxy 是专门做「转发」的根类,不继承自 NSObject,实现 forwardInvocation: 与 methodSignatureForSelector:,把消息转给 weak 持有的 target 即可;内存上 proxy 只多一个 weak 指针,不增加 target 的引用计数。
3.3 WeakProxy 示例(Objective-C)
@interface WeakProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation WeakProxy
+ (instancetype)proxyWithTarget:(id)target {
WeakProxy *p = [WeakProxy alloc];
p.target = target;
return p;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
WeakProxy *proxy = [WeakProxy proxyWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(onTick) userInfo:nil repeats:YES];
3.4 Timer 管理要点小结
| 要点 | 说明 |
|---|
| invalidate | VC(或持有 timer 的对象)dealloc 前必须调用 [timer invalidate],否则 RunLoop 持有 timer,timer 又强引用 target,导致泄漏或野指针。 |
| block 版 API(iOS 10+) | +[NSTimer scheduledTimerWithTimeInterval:repeats:block:] 的 block 里用 [weak self],timer 不直接强引用 self,可避免 timer→self 的强引用;仍需在 dealloc 里 invalidate。 |
| 子线程 | 子线程 RunLoop 默认不跑,timer 需加到 RunLoop 并 run;线程结束时记得 invalidate。 |
四、weak 实现思路(简述)
4.1 全局 weak 表
- 运行时维护全局的 weak 表(与对象地址关联):记录「哪些 weak 指针正在指向该对象」。
- 当对象 dealloc 时,查该表,把表中所有 weak 指针置为 nil,再销毁对象。
4.2 SideTable 与 weak_table(概念)
- 为减少锁竞争,常用 SideTable 分片:根据对象地址映射到某一张 SideTable;每张表内有 weak_table,存「对象 → 指向它的 weak 指针列表」。
- storeWeak 等函数:在注册 weak 指针、对象释放时更新对应 SideTable 中的 weak 表。
4.3 流程图:对象释放时 weak 置 nil
flowchart TB
A[对象 dealloc] --> B[查 weak 表]
B --> C[遍历指向该对象的 weak 指针]
C --> D[将每个 weak 指针置为 nil]
D --> E[销毁对象]
五、思维导图小结
mindmap
root((weak 与循环引用))
weak 语义
不增加引用计数
对象释放时置 nil
循环引用
成环 无法释放
破除 一方改 weak
Block
weak self
strong self 防提前释放
NSProxy 与 Timer
Timer 强引用 target
WeakProxy 转发 破循环
实现
SideTable weak_table
dealloc 时清空 weak
参考文献