循环引用底层原理
- 内存管理角度:
- 在Objective - C中,对象通过引用计数来管理内存。当一个对象被创建时,其引用计数为1。每次被其他对象强引用(持有),引用计数加1;每次不再被其他对象强引用(释放),引用计数减1。当引用计数变为0时,对象内存被释放。
- 当block捕获外部变量时,如果捕获的是对象类型变量,默认会对该对象进行强引用。例如,在一个类的实例方法中定义block,block捕获了
self
,此时block会强引用self
。如果self
又强引用了block(比如将block作为属性持有),就形成了self -> block -> self
这样的强引用环,导致self
和block的引用计数都不会降为0,内存无法释放,造成循环引用。
- ARC机制角度:
- ARC(自动引用计数)会自动插入内存管理代码,比如
retain
、release
和autorelease
。在循环引用场景下,ARC无法自动打破这个强引用环。因为ARC的工作是基于对象之间的引用关系,它不知道如何打破这种互相强引用的情况。例如,对于self -> block -> self
的环,ARC会按照正常的引用计数规则增加和减少引用计数,但由于这个环的存在,引用计数永远不会变为0。
优化方案及其适用场景
- 使用__weak关键字:
- 适用场景:适用于绝大多数场景,尤其是在类的实例方法中使用block且可能出现循环引用的情况。
- 示例:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
// 使用strongSelf操作self相关内容
[strongSelf doSomething];
}
};
- 原理:
__weak
修饰的变量不会增加对象的引用计数,所以不会形成强引用环。这里先使用__weak
创建一个弱引用weakSelf
,在block内部为了防止weakSelf
在block执行期间被释放,可以通过__strong
将其提升为强引用strongSelf
。这样既可以在block内安全地使用self
相关内容,又避免了循环引用。
- 使用__unsafe_unretained关键字:
- 适用场景:在ARC环境下,与
__weak
类似,但适用于对性能要求较高且确定对象不会在block执行期间被释放的场景。因为__unsafe_unretained
修饰的变量不会自动置为nil
,如果对象提前释放,访问它会导致野指针错误。
- 示例:
__unsafe_unretained typeof(self) unsafeSelf = self;
self.block = ^{
// 使用unsafeSelf操作self相关内容
[unsafeSelf doSomething];
};
- 原理:
__unsafe_unretained
修饰的变量不增加对象的引用计数,与__weak
不同的是,当对象被释放时,__unsafe_unretained
修饰的指针不会自动置为nil
,需要开发者自己确保对象在使用期间不会被释放。
- 在block执行完毕后手动打破循环引用:
- 适用场景:适用于block只执行一次且知道block何时执行完毕的场景,比如网络请求的回调block。
- 示例:
self.block = ^{
// 执行网络请求等操作
[self doNetworkRequestWithCompletion:^{
// 网络请求完成
self.block = nil;
}];
};
- 原理:在block执行完毕后,将持有block的属性(如这里的
self.block
)置为nil
,打破self
对block的强引用,从而打破循环引用。
- 使用中间对象打破循环引用:
- 适用场景:当直接使用
__weak
或__unsafe_unretained
不方便时,比如在复杂的类继承结构或多层嵌套的block场景下。
- 示例:可以创建一个中间对象,这个中间对象不被
self
和block直接强引用,通过中间对象来传递数据,避免self
和block之间的直接互相强引用。具体实现根据不同业务场景会有所不同。
- 原理:通过引入中间对象,改变对象之间的引用关系,避免形成
self -> block -> self
这样的强引用环。