一、什么是循环引用
循环引用指两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。如声明一个delegate一般用assign而不能用retain或strong,因为你一旦那么做了,很大可能引起循环引用。在以往的项目中,我几次用动态内存检查发现了循环引用导致的内存泄露。
二、block中的循环引用
这里讲的是block的循环引用问题,因为block在拷贝到堆上的时候(为什么要拷贝到堆上?见下面补充),会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用,如:
- (void)dealloc
{
NSLog(@"no cycle retain");
}
- (id)init
{
self = [super init];
if (self) {
#if TestCycleRetainCase1
//会循环引用
self.myblock = ^{
[self doSomething];
};
#elif TestCycleRetainCase2
//会循环引用
__block TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase3
//不会循环引用
__weak TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase4
//不会循环引用
__unsafe_unretained TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#endif
NSLog(@"myblock is %@", self.myblock);
}
return self;
}
- (void)doSomething
{
NSLog(@"do Something");
}
int main(int argc, char *argv[]) {
@autoreleasepool {
TestCycleRetain* obj = [[TestCycleRetain alloc] init];
obj = nil;
return 0;
}
}
经过上面的ARC环境测试发现,在加了__weak
和__unsafe_unretained
的变量引入后,TestCycleRetain方法可以正常执行dealloc方法,而不转换和用__block转换的变量都会引起循环引用。
但是实际情况是:
1)MRC情况下,用__block可以消除循环引用。
2)ARC情况下,必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak
,之前则只能使用__unsafe_unretained
了,__unsafe_unretained
缺点是指针释放后自己不会置空。
示例代码:(关于使用block会防止循环引用的代码示例)
1)在ARC下,由于__block
抓取的变量一样会被Block retain,所以必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak
,之前则只能使用__unsafe_unretained
,__unsafe_unretained
缺点是指针释放后自己不会置空。示例代码:
//iOS 5之前可以用__unsafe_unretained
//__unsafe_unretained typeof(self) weakSelf = self;
__weak typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt)
{
//使用weakSelf访问self成员
[weakSelf anotherFunc];
};
2)在非ARC下,显然无法使用弱引用,这里就可以直接使用__block来修饰变量,它不会被Block所retain的,参考代码:
//非ARC
__block typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt)
{
//使用weakSelf访问self成员
[weakSelf anotherFunc];
};
三、补充:为甚么要将block要拷贝到堆上?
一般来说我们总会在设置Block之后,在合适的时间回调Block,而不希望回调Block的时候Block已经被释放了,所以我们需要对Block进行copy,copy到堆中,以便后用。
当一个Block被Copy的时候,如果你在Block里进行了一些调用,那么将会有一个强引用指向这些调用方法的调用者(也就是上面讲的,会存在循环引用导致内存泄露的风险),其有两个规则:
如果你是通过引用来访问一个实例变量,那么将强引用至self
如果你是通过值来访问一个实例变量,那么将直接强引用至这个“值”变量