本文不講block如何聲明及使用,只講block在使用過程中暫時遇到及帶來的隱性危險。
主要基於兩點進行演示:
1.block 的循環引用(retain cycle)
2.去除block產生的告警時,需注意問題。
有一次,朋友問我當一個對象中的block塊中的訪問自己的屬性會不會造成循環引用,我哈綽綽的就回了一句,不會。兄弟,看完這個,希望你能理解我為什麼會說不會循環引用。別廢話,演示開始。
下面是我專們寫了一個類來演示:
頭文件.h
// // BlockDemo.h // blockDemo // // Created by apple on 14-7-24. // Copyright (c) 2014年 fengsh. All rights reserved. /* -fno-objc-arc 由於Block是默認建立在棧上, 所以如果離開方法作用域, Block就會被丟棄, 在非ARC情況下, 我們要返回一個Block ,需要 [Block copy]; 在ARC下, 以下幾種情況, Block會自動被從棧復制到堆: 1.被執行copy方法 2.作為方法返回值 3.將Block賦值給附有__strong修飾符的id類型的類或者Blcok類型成員變量時 4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中傳遞的時候. */ #import@class BlockDemo; typedef void(^executeFinishedBlock)(void); typedef void(^executeFinishedBlockParam)(BlockDemo *); @interface BlockDemo : NSObject { executeFinishedBlock finishblock; executeFinishedBlockParam finishblockparam; } /** * 執行結果 */ @property (nonatomic,assign) NSInteger resultCode; /** * 每次調用都產生一個新對象 * * @return */ + (BlockDemo *)blockdemo; /** * 不帶參數的block * * @param block */ - (void)setExecuteFinished:(executeFinishedBlock)block; /** * 帶參數的block * * @param block */ - (void)setExecuteFinishedParam:(executeFinishedBlockParam)block; - (void)executeTest; @end
// // BlockDemo.m // blockDemo // // Created by apple on 14-7-24. // Copyright (c) 2014年 fengsh. All rights reserved. // #if __has_feature(objc_arc) && __clang_major__ >= 3 #define OBJC_ARC_ENABLED 1 #endif // __has_feature(objc_arc) #if OBJC_ARC_ENABLED #define OBJC_RETAIN(object) (object) #define OBJC_COPY(object) (object) #define OBJC_RELEASE(object) object = nil #define OBJC_AUTORELEASE(object) (object) #else #define OBJC_RETAIN(object) [object retain] #define OBJC_COPY(object) [object copy] #define OBJC_RELEASE(object) [object release], object = nil #define OBJC_AUTORELEASE(object) [object autorelease] #endif #import "BlockDemo.h" @implementation BlockDemo + (BlockDemo *)blockdemo { return OBJC_AUTORELEASE([[BlockDemo alloc]init]); } - (id)init { self = [super init]; if (self) { NSLog(@"Object Constructor!"); } return self; } - (void)dealloc { NSLog(@"Object Destoryed!"); #if !__has_feature(objc_arc) [super dealloc]; #endif } - (void)setExecuteFinished:(executeFinishedBlock)block { OBJC_RELEASE(finishblock); finishblock = OBJC_COPY(block); //在非ARC下這裡不能使用retain } - (void)setExecuteFinishedParam:(executeFinishedBlockParam)block { OBJC_RELEASE(finishblockparam); finishblockparam = OBJC_COPY(block); //在非ARC下這裡不能使用retain } - (void)executeTest { [self performSelector:@selector(executeCallBack) withObject:nil afterDelay:5]; } - (void)executeCallBack { _resultCode = 200; if (finishblock) { finishblock(); } if (finishblockparam) { finishblockparam(self); } } @end
在非ARC環境下
執行下在語句的測試:
- (IBAction)onTest:(id)sender { BlockDemo *demo = [[[BlockDemo alloc]init]autorelease]; [demo setExecuteFinished:^{ if (demo.resultCode == 200) { NSLog(@"call back ok."); } }]; [demo executeTest]; }
2014-07-24 19:08:04.852 blockDemo[25104:60b] Object Constructor! 2014-07-24 19:08:09.854 blockDemo[25104:60b] call back ok.
__block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];在非ARC下,只雖一個__block關鍵詞就可以。相對還是簡單的。
好下面再來看一下在ARC模式下的block循環引用又是怎麼樣的。
在ARC模式下
執行下面語句:
- (IBAction)onTest:(id)sender { BlockDemo *demo = [[BlockDemo alloc]init]; [demo setExecuteFinished:^{ if (demo.resultCode == 200) { NSLog(@"call back ok."); } }]; [demo executeTest]; }
2014-07-24 19:20:33.997 blockDemo[25215:60b] Object Constructor! 2014-07-24 19:20:39.000 blockDemo[25215:60b] call back ok.同樣會被引入循環。
相信看到這裡的人,大多都要噴了,這哪個不知道呀,還知道怎麼解決呢,非ARC中加了個__block,當然的在ARC中加一個__weak就搞定了。嗯,確實是這樣,但別急,接著往下看,絕對有收獲。在這裡先自己默認想一下,你是如何加這個__weak的。
對於第一個問是點block 的循環引用(retain cycle)到這裡暫告結束。下面講第二點。因為block告警在非ARC 中暫未發現因寫法引入(如果你知道,麻煩告訴我怎麼弄產生告警,我好研究一下。)
下面講在ARC模式下去除因寫法產生的告警時需要注意的問題。
像上面的寫法其實在ARC中會產生(Capturing 'demo' strongly in this block is likely to lead to a retain cycle)告警。如下圖:
在ARC中,編譯器智能化了,直接提示這樣寫會產生循環引用。因此很多愛去除告警的朋友就會想法去掉,好,咱再來看去掉時需注意的問題。
情況一:
- (IBAction)onTest:(id)sender { __weak BlockDemo *demo = [[BlockDemo alloc]init]; [demo setExecuteFinished:^{ if (demo.resultCode == 200) { NSLog(@"call back ok."); } }]; [demo executeTest]; }直接在前面加一個__weak,但這樣真的沒有告警了嗎?如果有,哪麼恭喜歡你,說明編譯器還幫你大忙。見下圖
這時還會告警,說這是一個WEAK變量,就馬上會被release。因此就不會執行block中的內容。大家可以運行一下看<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD48cD7K5LP2veG5+86qo7o8L3A+PHA+PC9wPjxwcmUgY2xhc3M9"brush:java;">2014-07-24 19:38:02.453 blockDemo[25305:60b] Object Constructor! 2014-07-24 19:38:02.454 blockDemo[25305:60b] Object Destoryed!很顯然,馬上被release了,所以block 中的代碼根本就不執行。
謝天謝地,幸好編譯器提前告訴了我們有這個隱性危險。相信大家為解決告警,又會得到一個比較圓滿的解決方案,見下:
- (IBAction)onTest:(id)sender { BlockDemo *demo = [[BlockDemo alloc]init]; __weak typeof(BlockDemo) *weakDemo = demo; [demo setExecuteFinished:^{ if (weakDemo.resultCode == 200) { NSLog(@"call back ok."); } }]; [demo executeTest]; }
2014-07-24 19:40:33.204 blockDemo[25328:60b] Object Constructor! 2014-07-24 19:40:38.206 blockDemo[25328:60b] call back ok. 2014-07-24 19:40:38.207 blockDemo[25328:60b] Object Destoryed!
- (IBAction)onTest:(id)sender { __weak BlockDemo *demo = [BlockDemo blockdemo]; //這裡才是重點,前面是[[BlockDemo alloc]init];會有告警。 [demo setExecuteFinished:^{ if (demo.resultCode == 200) { NSLog(@"call back ok."); } }]; [demo executeTest]; }
+ (BlockDemo *)blockdemo { return OBJC_AUTORELEASE([[BlockDemo alloc]init]); }不同點見下圖:真心看不到作何告警,是不是。但這存在什麼風險,風險就是運行的時候,block根本就沒有run。因為對象早就釋放了。
直接輸出:
2014-07-24 19:47:53.033 blockDemo[25395:60b] Object Constructor! 2014-07-24 19:47:53.035 blockDemo[25395:60b] Object Destoryed!
好,到了尾聲,來說說為什麼朋友問我block會不會引行死循環,我說不會的理由。
見碼:
- (IBAction)onTest:(id)sender { BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init]; [demo setExecuteFinishedParam:^(BlockDemo * ademo) { if (ademo.resultCode == 200) { NSLog(@"call back ok."); } }]; [demo executeTest]; }
由於我一直都這樣寫block,所以朋友一問起,我就說不會循環引用了,因為壓根他碰到的就是前面講述的哪種訪問方式,而我回答的是我的這種使用方式。正因為口頭描述,與實際回復真是差之千裡。。。哈哈。為了驗證我朋友的這個,我特意寫了個這篇文章,希望對大家有所幫助。最後,謝謝大家花時間閱讀。