[心得] 探討Objective-C Block (part 3)
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
MacDev 近期熱門文章
PTT數位生活區 即時熱門文章