block的一些基本使用阱洪,我已經(jīng)寫了一篇,有興趣的話也可以去看看比庄,block在開發(fā)中給我們帶了很多方便豫缨,然而使用block也有一些隱患,例如容易出現(xiàn)循環(huán)引用導(dǎo)致內(nèi)存泄露先嬉,這篇文章會剖析為什么會出現(xiàn)這種問題,如何去解決,各個對象在內(nèi)存中釋放的時機等一些問題应狱。
1.為什么會出現(xiàn)循環(huán)引用
解釋這個問題我會用一個小例子和圖示分析,請看事例:
@implementation RequestUtil
- (void)getRequestData{
[self.requester startWithCompletionHandler:^(NSData *data){
_fetchData = data;
}];
}
@end
假設(shè)requester和fetchData是RequestUtil這個類的兩個屬性祠丝,這段代碼片段貌似完全沒問題疾呻,然而它已經(jīng)出現(xiàn)循環(huán)引用了,那么上面的這段代碼內(nèi)存關(guān)系圖會是下面圖示的關(guān)系:
對象self強引用著對象requester,requester執(zhí)行startWithCompletionHandler方法的時候写半,會對block進行強引用岸蜗,在block中要訪問對象self中的屬性,首先得對對象self強引用叠蝇,只有引用了self才能訪問對象self中的屬性璃岳,這樣一來就出現(xiàn)一個閉環(huán),誰也無法釋放悔捶,這塊內(nèi)存一直被占用著铃慷,從而導(dǎo)致內(nèi)存泄露。
2. 如何解決循環(huán)引用
解決循環(huán)引用的思路是打破上圖所示的閉環(huán)蜕该,下面介紹兩種方法解決這個問題犁柜。
1.解決方案一:
@implementation RequestUtil
- (void)getRequestData{
__weak typeof(self) weakSelf = self;
[self.requester startWithCompletionHandler:^(NSData *data{
weakSelf.fetchData = data;
}];
}
@end
上面這段代碼就完美的解決了循環(huán)引用的問題,這段代碼在內(nèi)存中的關(guān)系如圖:
通過創(chuàng)建一個weak類型的指針指向self對象堂淡,block去強引用這個weakSelf就不會出現(xiàn)循環(huán)引用了馋缅。在內(nèi)存中釋放的順序是,先self釋放绢淀,self釋放由于沒有任何對象強引用requester萤悴,requester隨著釋放,之后block釋放皆的,weakSelf也沒有任何對象引用覆履,也從內(nèi)存中釋放。
2.解決方案二:
在合適的地方,手動將block或者requester釋放掉硝全,打破閉環(huán)怪嫌。
- 手動釋放requester
@implementation RequestUtil
- (void)getRequestData{
[self.requester startWithCompletionHandler:^(NSData *data){
_fetchData = data;
_requester = nil;
}];
}
@end
上面這段代碼解決了循環(huán)引用的問題,這段代碼在內(nèi)存中的關(guān)系如圖:
當(dāng)requester指向nil的時候柳沙,它所引用的block由于沒有任何對象引用它岩灭,block會在內(nèi)存中被釋放掉,block被釋放后不再強引用self赂鲤,當(dāng)沒有其它指針強引用self的時候噪径,self會被釋放,requester也就隨著釋放数初,從而都從內(nèi)存中釋放了找爱。
- 手動釋放blcok
@implementation RequestUtil
- (void)getRequestData{
[self.requester startWithCompletionHandler:^(NSData *data){
_fetchData = data;
}];
}
@end
@implementation XXXXXXX
- (void)startWithCompletionHandler:(void (^)(NSData *data)) block{
// do something
NSData *pngData = UIImagePNGRepresentation([UIImage imageNamed:@"pngName"]);
block(pngData);
block = nil; // 一定要確保block執(zhí)行完畢才可這么做,如果block中又有多線程泡孩,那么這么做就不合適了车摄,總之block要在合適的時間釋放
}
@end
上面這段代碼也解決了循環(huán)引用的問題,這段代碼在內(nèi)存中的關(guān)系如圖:
和手動釋放requester一個道理仑鸥,將block指向nil吮播,self會在需要釋放的時候釋放,requester也隨著釋放眼俊,從而都從內(nèi)存中釋放了意狠。
3.棧塊和堆塊
定義塊的時候,其所占的內(nèi)存是分配在棧區(qū)的疮胖,也就是說环戈,塊只在定義它的那個范圍有效。請看事例:
void (^block)();
if(flag){
block = ^{ // do something };
} else {
block = ^{ // do something };
}
block();
這段代碼看上去似乎很OK澎灸,其實是有風(fēng)險的院塞,定義在if和else中的兩個block都分配在棧內(nèi)存中,等離開了相應(yīng)的if語句塊或else語句塊范圍之后性昭,編譯器有可能把分配給block的內(nèi)存覆寫掉拦止。于是,這兩個塊只能保證在對應(yīng)的if或else語句范圍內(nèi)有效巩梢。這樣的代碼編譯完全沒問題的创泄,運行起來就看人品了艺玲,若編譯器未覆寫block在棧區(qū)所占用的內(nèi)存括蝠,則程序可以正常運行,若覆寫或回收了程序會崩潰饭聚。
解決辦法是給塊發(fā)送copy消息忌警,使之拷貝到堆內(nèi)存中,這樣塊就成了帶引用計數(shù)的對象了。這也是為什么block要用copy修飾的原因
void (^block)();
if(flag){
block = [^{ // do something } copy];
} else {
block = [^{ // do something } copy];
}
block();
這樣代碼就安全了法绵,如果是采用非ARC的箕速,那么最后記得手動釋放塊在堆區(qū)所分配的內(nèi)存。