,经常说在 block 里面很容易导致循环引用(retain cycle),可是为什么会导致循环引用,却不是特别理解,最近研究了,也算是做一个总结.
常见的循环引用
看看这个类
代码1
@interface TestClass : NSObject
@property (nonatomic,copy)void (^block)();
-(void)test;
@end
有一个属性, block,一个方法 test
来看看类实现
代码2
@implementation TestClass
-(void)dealloc{
NSLog(@"dealloc");
}
-(void)test{
self.block=^{
NSLog(@"%@",self);
};
}
@end
可以看到, test
方法中有一个明显的在 self.block
中调用self
的过程.来看看会不会导致循环引用.
接下来,随便在其他类中来调用一下这个类
代码3
TestClass* test=[TestClass new];
[test test];
运行试试
2016-01-29 10:55:14.035 test[16318:1121947] <TestClass: 0x7fd3eb52b250>**
没有输出 "dealloc",说明 test对象没有被释放,怎么办?
很简单就会想到使用弱引用,
来看看使用弱引用的代码
代码4
-(void)test{
__weak typeof(self) weakself=self;
self.block=^{
NSLog(@"%@",weakself);
};
self.block();
}
输出
2016-01-29 10:58:01.899 test[16360:1124161] <TestClass: 0x7f9d61c4c240>
2016-01-29 10:58:01.899 test[16360:1124161] dealloc
可以看到, self
被成功释放了,没有被循环引用.
为什么?
首先, block
会自动捕获block
内部使用的变量,并对其强引用.具体过程可以参考 Objective-C高级编程.
在本例中代码2, block
内部有 self
,因为self
本身是一个强引用,所以block
会再给 self
加上一个强引用.
而block
本身是self
的一个属性,self
也强持有block
.
这就出现一个问题, self
强持有block
,block
强持有self
,当self
想要释放的时候,要看一下有没有自己强引用,而 block
刚好强持有自己,所以,暂时没办法释放.block
想要释放的时候,却发现 self
强持有自己,所以也得不到释放,相互纠缠.
而为何代码4 又可以呢?
代码4中, block
内部有一个 weakself
,weakself
本身是一个弱引用,由于对弱引用无法强持有,所以, block 并没有强持有self
,当 self 想要释放的时候,已经没有其他强引用了,就可以释放.self
被释放,就没有变量强持有block
,block
也会释放.这样,循环引用就不存在了.
引申
既然只要不相互持有就可以,那么我们手动的去掉强引用还会不会发生循环引用呢?
看看这个代码.
代码5
-(void)test{
self.block=^{
NSLog(@"%@",self);
};
self.block();
self.block=nil;
}
这里在执行完毕后,手动去掉了 block 的强引用.由于打破了相互引用,自然也不会有循环引用.
看看输出
2016-01-29 11:25:25.238 test[16439:1133368] <TestClass: 0x7fb381511b80>
2016-01-29 11:25:25.239 test[16439:1133368] dealloc
果然如此,不过这种方法毕竟很不安全,要是block
是需要在一段时间后调用的,如果你马上就释放了,那么 block 中代码就不能执行.又或者,如果你忘记手动释放,也会造成很大麻烦,相对之下,使用 weakself
就好多了.
另外,在使用weakself
的时候,可以和strongself
一起使用,可以参考我的文章为什么要用 strongSelf