[心得] 探討Objective-C Block (part 3)

看板MacDev作者 (畢業了..@@")時間14年前 (2011/08/30 23:17), 編輯推噓1(100)
留言1則, 1人參與, 最新討論串1/1
Blog版 http://popcornylu.blogspot.com/2011/08/objective-c-block-part-3.html 前面講了簡單的block用法跟block variable, 但是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 #import <Foundation/Foundation.h> typedef 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呢? 其實跟我們之前聊到的 http://popcornylu.blogspot.com/2011/07/delegate.html (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永遠不會歸零的狀況。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 114.32.239.120 ※ 編輯: popcorny 來自: 114.32.239.120 (08/30 23:18)

08/31 06:36, , 1F
推這篇
08/31 06:36, 1F
文章代碼(AID): #1ENFySKg (MacDev)
文章代碼(AID): #1ENFySKg (MacDev)