但是block難的地方應該就是記憶體管理的部份,
如果你不是很瞭解block內部的記憶體管理,
很容易一個不小心就導致circular reference而導致memory leakage..
前篇有提到,我們可以把block當作參數傳給function或是message,
但是傳進去後,有可能這個function會想把你的block pointer留下來
最常見的就是做event handling的例子,
我們把一個事件觸發的block當作參數丟進來,
但是是件觸發可能是數秒之後的事情,
而此時註event handler的function/message已經return了,
那此block所reference的local變數可能已經invalid了,
這時候block要怎麼處理這樣的情形呢?
首先,在block的定義中,此block還停留在call stack之中,
也就是他的生命週期會隨著定義此block的function return之後,其生命週期就會結束
除非我們呼叫
block_copy
或是[myblock copy]
此時block才會從stack變到heap中。
之後我們才可以把參數傳過來的block指給instance variable或是global variable,
而block中所用到的物件在此同時reference count也會+1。
但reference count +1這件事情卻在每一種case不一樣
因為block內部可以使用環境中看的到local, block, instance, local static, global variable
那copy這個動作會發生什麼事情呢?
我們先寫一個範例code
//MyBlockTest.h #importtypedef void (^myBlockTest_type)(void); @interface MyBlockTest : NSObject { NSObject* instanceRef; myBlockTest_type myBlock; } - (void) test; @end
//MyBlockTest.m #import "MyBlockTest.h" @implementation MyBlockTest static NSObject* globalRef; +(void) initialize { globalRef = [NSObject new]; } - (id)init { self = [super init]; if(self) { instanceRef = [NSObject new]; } return self; } - (void) test { // Local variable NSObject* localRef = [NSObject new]; // Block variable __block NSObject* blockRef = [NSObject new]; // Local static variable static NSObject* localStaticRef; if(!localStaticRef) localStaticRef = [NSObject new]; // create a block myBlockTest_type aBlock = ^{ NSLog(@"%@", localRef); NSLog(@"%@", blockRef); NSLog(@"%@", instanceRef); NSLog(@"%@", globalRef); NSLog(@"%@", localStaticRef); }; //copy the block myBlock = [aBlock copy]; NSLog(@"%d", [localRef retainCount]); NSLog(@"%d", [blockRef retainCount]); NSLog(@"%d", [instanceRef retainCount]); NSLog(@"%d", [globalRef retainCount]); NSLog(@"%d", [localStaticRef retainCount]); [localRef release]; } @end
大家可以先想想看,當呼叫test的時候,會印出什麼樣的結果?
正確的答案是
2 1 1 1 1
不知道你答對了沒?
第一個
localRef
應該最能夠理解,基本上就是+1,這個就是這樣設計。第二個
blockRef
,由前面一張對block variable的解釋,我們可以知道block variable是一個closure用一份。
因此此block variable並沒有額外的retain的動作。
所以被block variable指到的物件也不會有reference count +1的情況。
第三個
instanceRef
為什麼沒有+1呢?事實上這個問題也是挺有陷阱題的味道。
對block來講,他看到的是self這個變數,而非instanceRef。
所以ref. count +1的不是instanceRef而是self。
如果在block copy的前後各把self的ref count印出來你就可以佐證這個事實了。
第四個
globalRef
跟第五個localStaticRef
本質上很像,所以兩個可以一起討論。由於這兩個變數在runtime中的位置是固定而且唯一的,
所以基本上在block內用上面兩個變數跟block沒有什麼兩樣。
因此block copy並不會也不需要增加ref. count的數目。
瞭解之後,那什麼時候可能會出現circular reference呢?
其實跟我們之前聊到的ios delegate你必須知道的事情所說的內容很像。
只是這次主角從delegate換成block。
試想,如果有3個view controller,分別是VC1, VC2, VC3
如果VC1產生並retain VC2
VC2也產生VC3
而且VC2可能跟VC3註冊了一個event handler並且參數是用一個block。
在這個block中可能長這樣。
[vc3 setOnClose:^{ [self dismissModalViewControllerAnimated:YES]; }];那這樣會發生什麼是情呢?
答案是當VC1 release VC2的時候,
VC2因為自己有參照VC3,所以VC3的retain count還是1
VC3因為他的instance variable有retain這個block
而這個block因為用到block中的self
這個self就是VC2,
那這樣可糟了個糕,circular的悲劇就產生了。
目前官方文件告訴我們要這樣做
__block VC2* tempVC2 = self; [vc3 setOnClose:^{ [tempVC2 dismissModalViewControllerAnimated:YES]; }];我們透過block variable不會retain的特性,
來把self丟給tempVC2,
如此在block在被copy的時候不會增加retain count。
我只能說太不friendly了,
不過目前好像也只有這樣解,而且到了ARC之後這個問題還是存在。
所以大家一定要改清楚block的memory management,
才不會不知道為什麼,reference count永遠不會歸零的狀況。
探討Objective-C Block (part 1) - block的使用
探討Objective-C Block (part 2) - block變數
探討Objective-C Block (part 3) - block的記憶體管理
你能讲讲block的嵌套应用时变量的引用情况么, 以及下面的这种情况
回覆刪除[vc3 setOnClose:^{
[tempVC2 dismissModalViewControllerAnimated:YES];
}];
这种情况经常看到不对传入的block进行copy操作, 而且也能执行回调, 不会出错, 我看到一些第三方开源库(AFNetworking等)中没有copy, 当然也有部分copy了, 所以有些不解
我的邮件:willonboyzhang$gmail.com