一、什么是循環(huán)引用
循環(huán)引用指兩個(gè)對(duì)象相互強(qiáng)引用了對(duì)方,即retain了對(duì)方,從而導(dǎo)致誰也釋放不了誰的內(nèi)存泄露問題。如聲明一個(gè)delegate一般用assign而不能用retain或strong,因?yàn)槟阋坏┠敲醋隽耍艽罂赡芤鹧h(huán)引用。在以往的項(xiàng)目中,我?guī)状斡脛?dòng)態(tài)內(nèi)存檢查發(fā)現(xiàn)了循環(huán)引用導(dǎo)致的內(nèi)存泄露。
二、block中的循環(huán)引用
這里講的是block的循環(huán)引用問題,因?yàn)?strong>block在拷貝到堆上的時(shí)候(為什么要拷貝到堆上?見下面補(bǔ)充),會(huì)retain其引用的外部變量,那么如果block中如果引用了他的宿主對(duì)象,那很有可能引起循環(huán)引用,如:
- (void)dealloc
{
NSLog(@"no cycle retain");
}
- (id)init
{
self = [super init];
if (self) {
#if TestCycleRetainCase1
//會(huì)循環(huán)引用
self.myblock = ^{
[self doSomething];
};
#elif TestCycleRetainCase2
//會(huì)循環(huán)引用
__block TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase3
//不會(huì)循環(huán)引用
__weak TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase4
//不會(huì)循環(huán)引用
__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;
}
}
經(jīng)過上面的ARC環(huán)境測(cè)試發(fā)現(xiàn),在加了__weak和__unsafe_unretained的變量引入后,TestCycleRetain方法可以正常執(zhí)行dealloc方法,而不轉(zhuǎn)換和用__block轉(zhuǎn)換的變量都會(huì)引起循環(huán)引用。
但是實(shí)際情況是:
1)MRC情況下,用__block可以消除循環(huán)引用。
2)ARC情況下,必須用弱引用才可以解決循環(huán)引用問題,iOS 5之后可以直接使用__weak,之前則只能使用__unsafe_unretained了,__unsafe_unretained缺點(diǎn)是指針釋放后自己不會(huì)置空。
示例代碼:(關(guān)于使用block會(huì)防止循環(huán)引用的代碼示例)
1)在ARC下,由于__block抓取的變量一樣會(huì)被Block retain,所以必須用弱引用才可以解決循環(huán)引用問題,iOS 5之后可以直接使用__weak,之前則只能使用__unsafe_unretained,__unsafe_unretained缺點(diǎn)是指針釋放后自己不會(huì)置空。示例代碼:
//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來修飾變量,它不會(huì)被Block所retain的,參考代碼:
//非ARC
__block typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt)
{
//使用weakSelf訪問self成員
[weakSelf anotherFunc];
};
三、補(bǔ)充:為甚么要將block要拷貝到堆上?
一般來說我們總會(huì)在設(shè)置Block之后,在合適的時(shí)間回調(diào)Block,而不希望回調(diào)Block的時(shí)候Block已經(jīng)被釋放了,所以我們需要對(duì)Block進(jìn)行copy,copy到堆中,以便后用。
當(dāng)一個(gè)Block被Copy的時(shí)候,如果你在Block里進(jìn)行了一些調(diào)用,那么將會(huì)有一個(gè)強(qiáng)引用指向這些調(diào)用方法的調(diào)用者(也就是上面講的,會(huì)存在循環(huán)引用導(dǎo)致內(nèi)存泄露的風(fēng)險(xiǎn)),其有兩個(gè)規(guī)則:
如果你是通過引用來訪問一個(gè)實(shí)例變量,那么將強(qiáng)引用至self
如果你是通過值來訪問一個(gè)實(shí)例變量,那么將直接強(qiáng)引用至這個(gè)“值”變量