寫給喜歡用Block的朋友(ios Block)

做者:fengsh998安全

原文地址:http://blog.csdn.net/fengsh998/article/details/38090205微信

轉載請註明出處app

若是以爲文章對你有所幫助,請經過留言或關注微信公衆賬號fengsh998來支持我,謝謝!框架



本文不講block如何聲明及使用,只講block在使用過程當中暫時遇到及帶來的隱性危險。測試

主要基於兩點進行演示:this

1.block 的循環引用(retain cycle)atom

2.去除block產生的告警時,需注意問題。spa


有一次,朋友問我當一個對象中的block塊中的訪問本身的屬性會不會形成循環引用,我哈綽綽的就回了一句,不會。兄弟,看完這個,但願你能理解我爲何會說不會循環引用。別廢話,演示開始。.net


下面是我專們寫了一個類來演示:code

頭文件.h

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. //  

  2. //  BlockDemo.h  

  3. //  blockDemo  

  4. //  

  5. //  Created by apple on 14-7-24.  

  6. //  Copyright (c) 2014年 fengsh. All rights reserved.  

  7. /* 

  8.  -fno-objc-arc 

  9.   

  10.  因爲Block是默認創建在棧上, 因此若是離開方法做用域, Block就會被丟棄, 

  11.  在非ARC狀況下, 咱們要返回一個Block ,須要 [Block copy]; 

  12.   

  13.  在ARC下, 如下幾種狀況, Block會自動被從棧複製到堆: 

  14.   

  15.  1.被執行copy方法 

  16.  2.做爲方法返回值 

  17.  3.將Block賦值給附有__strong修飾符的id類型的類或者Blcok類型成員變量時 

  18.  4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中傳遞的時候. 

  19.  */  

  20.   

  21. #import <Foundation/Foundation.h>  

  22.   

  23. @class  BlockDemo;  

  24.   

  25. typedef void(^executeFinishedBlock)(void);  

  26. typedef void(^executeFinishedBlockParam)(BlockDemo *);  

  27.   

  28. @interface  BlockDemo : NSObject  

  29. {  

  30.     executeFinishedBlock finishblock;  

  31.     executeFinishedBlockParam finishblockparam;  

  32. }  

  33.   

  34. /** 

  35.  *  執行結果 

  36.  */  

  37. @property (nonatomic,assign) NSInteger resultCode;  

  38.   

  39. /** 

  40.  *  每次調用都產生一個新對象 

  41.  * 

  42.  *  @return  

  43.  */  

  44. + (BlockDemo *)blockdemo;  

  45.   

  46. /** 

  47.  *  不帶參數的block 

  48.  * 

  49.  *  @param block 

  50.  */  

  51. - (void)setExecuteFinished:(executeFinishedBlock)block;  

  52.   

  53. /** 

  54.  *  帶參數的block 

  55.  * 

  56.  *  @param block 

  57.  */  

  58. - (void)setExecuteFinishedParam:(executeFinishedBlockParam)block;  

  59.   

  60. - (void)executeTest;  

  61.   

  62.   

  63. @end  


實現文件

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. //  

  2. //  BlockDemo.m  

  3. //  blockDemo  

  4. //  

  5. //  Created by apple on 14-7-24.  

  6. //  Copyright (c) 2014年 fengsh. All rights reserved.  

  7. //  

  8.   

  9. #if __has_feature(objc_arc) && __clang_major__ >= 3  

  10.     #define OBJC_ARC_ENABLED 1  

  11. #endif // __has_feature(objc_arc)  

  12.   

  13. #if OBJC_ARC_ENABLED  

  14.     #define OBJC_RETAIN(object)         (object)  

  15.     #define OBJC_COPY(object)           (object)  

  16.     #define OBJC_RELEASE(object)        object = nil  

  17.     #define OBJC_AUTORELEASE(object)    (object)  

  18. #else  

  19.     #define OBJC_RETAIN(object)           [object retain]  

  20.     #define OBJC_COPY(object)             [object copy]  

  21.     #define OBJC_RELEASE(object)          [object release], object = nil  

  22.     #define OBJC_AUTORELEASE(object)      [object autorelease]  

  23. #endif  

  24.   

  25. #import "BlockDemo.h"  

  26.   

  27. @implementation BlockDemo  

  28.   

  29.   

  30. + (BlockDemo *)blockdemo  

  31. {  

  32.     return OBJC_AUTORELEASE([[BlockDemo alloc]init]);  

  33. }  

  34.   

  35. - (id)init  

  36. {  

  37.     self = [super init];  

  38.     if (self) {  

  39.         NSLog(@"Object Constructor!");  

  40.     }  

  41.     return self;  

  42. }  

  43.   

  44. - (void)dealloc  

  45. {  

  46.     NSLog(@"Object Destoryed!");  

  47. #if !__has_feature(objc_arc)  

  48.     [super dealloc];  

  49. #endif  

  50. }  

  51.   

  52. - (void)setExecuteFinished:(executeFinishedBlock)block  

  53. {  

  54.     OBJC_RELEASE(finishblock);  

  55.     finishblock = OBJC_COPY(block); //在非ARC下這裏不能使用retain  

  56. }  

  57.   

  58. - (void)setExecuteFinishedParam:(executeFinishedBlockParam)block  

  59. {  

  60.     OBJC_RELEASE(finishblockparam);  

  61.     finishblockparam = OBJC_COPY(block); //在非ARC下這裏不能使用retain  

  62. }  

  63.   

  64. - (void)executeTest  

  65. {  

  66.     [self performSelector:@selector(executeCallBack) withObject:nil afterDelay:5];  

  67. }  

  68.   

  69. - (void)executeCallBack  

  70. {  

  71.     _resultCode = 200;  

  72.       

  73.     if (finishblock)  

  74.     {  

  75.         finishblock();  

  76.     }  

  77.       

  78.     if (finishblockparam)  

  79.     {  

  80.         finishblockparam(self);  

  81.     }  

  82. }  

  83.   

  84. @end  


上面是由於考慮到在ARC 和非ARC中進行編譯演示,因此我特地加了ARC預編譯判斷。主要是方便不要改動太多的代碼來給你們演示。


在非ARC環境下


執行下在語句的測試:

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. - (IBAction)onTest:(id)sender  

  2. {  

  3.     BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];  

  4.       

  5.     [demo setExecuteFinished:^{  

  6.         if (demo.resultCode == 200) {  

  7.             NSLog(@"call back ok.");  

  8.         }  

  9.     }];  

  10.       

  11.     [demo executeTest];  

  12.        

  13. }  


輸出結果:

[cpp] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. 2014-07-24 19:08:04.852 blockDemo[25104:60b] Object Constructor!  

  2. 2014-07-24 19:08:09.854 blockDemo[25104:60b] call back ok.  


很顯然。儘管demo 是局部變量,並autorelease但能夠看出天然至終並無獲得釋放,這是由於block中使用了 block內進行訪問了自身的resultCode屬性。相信不少朋友也都會解決這種循環引用問題。就是在變量前面加個__block,就像這樣。

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. __block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];  

在非ARC下,只雖一個__block關鍵詞就能夠。相對仍是簡單的。

好下面再來看一下在ARC模式下的block循環引用又是怎麼樣的。

在ARC模式下

執行下面語句:

[cpp] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. - (IBAction)onTest:(id)sender  

  2. {  

  3.     BlockDemo *demo = [[BlockDemo alloc]init];  

  4.     [demo setExecuteFinished:^{  

  5.         if (demo.resultCode == 200) {  

  6.             NSLog(@"call back ok.");  

  7.         }  

  8.     }];  

  9.       

  10.     [demo executeTest];  

  11.        

  12. }  


執行輸出結果:

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. 2014-07-24 19:20:33.997 blockDemo[25215:60b] Object Constructor!  

  2. 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中,編譯器智能化了,直接提示這樣寫會產生循環引用。所以不少愛去除告警的朋友就會想法去掉,好,咱再來看去掉時需注意的問題。

狀況一:

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. - (IBAction)onTest:(id)sender  

  2. {  

  3.     __weak BlockDemo *demo = [[BlockDemo alloc]init];  

  4.     [demo setExecuteFinished:^{  

  5.         if (demo.resultCode == 200) {  

  6.             NSLog(@"call back ok.");  

  7.         }  

  8.     }];  

  9.     [demo executeTest];  

  10. }  

直接在前面加一個__weak,但這樣真的沒有告警了嗎?若是有,哪麼恭喜歡你,說明編譯器還幫你大忙。見下圖



這時還會告警,說這是一個WEAK變量,就立刻會被release。所以就不會執行block中的內容。你們能夠運行一下看

輸出結果爲:

[cpp] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. 2014-07-24 19:38:02.453 blockDemo[25305:60b] Object Constructor!  

  2. 2014-07-24 19:38:02.454 blockDemo[25305:60b] Object Destoryed!  

很顯然,立刻被release了,因此block 中的代碼根本就不執行。

謝天謝地,幸虧編譯器提早告訴了咱們有這個隱性危險。相信你們爲解決告警,又會獲得一個比較圓滿的解決方案,見下:

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. - (IBAction)onTest:(id)sender  

  2. {  

  3.     BlockDemo *demo = [[BlockDemo alloc]init];  

  4.       

  5.     __weak typeof(BlockDemo) *weakDemo = demo;  

  6.       

  7.     [demo setExecuteFinished:^{  

  8.         if (weakDemo.resultCode == 200) {  

  9.             NSLog(@"call back ok.");  

  10.         }  

  11.     }];  

  12.     [demo executeTest];  

  13. }  


這樣寫,即去除了告警又保證了block的運行。這纔是咱們最終想要的結果。
輸出爲:

[cpp] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. 2014-07-24 19:40:33.204 blockDemo[25328:60b] Object Constructor!  

  2. 2014-07-24 19:40:38.206 blockDemo[25328:60b] call back ok.  

  3. 2014-07-24 19:40:38.207 blockDemo[25328:60b] Object Destoryed!  


但你們別得意。有提示,相信你們都能處理,並獲得個好的解決方法。哪麼下面大來再來看一下這個寫法,讓你真心甘拜下風。。。。。

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. - (IBAction)onTest:(id)sender  

  2. {  

  3.     __weak BlockDemo *demo = [BlockDemo blockdemo]; //這裏纔是重點,前面是[[BlockDemo alloc]init];會有告警。  

  4.       

  5.     [demo setExecuteFinished:^{  

  6.         if (demo.resultCode == 200) {  

  7.             NSLog(@"call back ok.");  

  8.         }  

  9.     }];  

  10.     [demo executeTest];  

  11. }  


其實只是把init放到了類方法中進行書寫而已,但會有什麼不一樣。

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. + (BlockDemo *)blockdemo  

  2. {  

  3.     return OBJC_AUTORELEASE([[BlockDemo alloc]init]);  

  4. }  

不一樣點見下圖:真心看不到做何告警,是否是。但這存在什麼風險,風險就是運行的時候,block根本就沒有run。由於對象早就釋放了。


直接輸出:

[cpp] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. 2014-07-24 19:47:53.033 blockDemo[25395:60b] Object Constructor!  

  2. 2014-07-24 19:47:53.035 blockDemo[25395:60b] Object Destoryed!  


所以,寫這個主要用來告戒一些喜歡用BLOCK但又想固然的朋友,有一些朋友喜歡去除告警,但只是盲目的加上__weak 或__block關鍵語,每每可能存在一些重大的安全隱患。就像演示中block根本不走。若是到了發佈時,爲了去告警而這樣簡單的處理了,並無進行測試就打包。哪麼將死得很慘。。。。。


好,到了尾聲,來講說爲何朋友問我block會不會引行死循環,我說不會的理由。

見碼:

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. - (IBAction)onTest:(id)sender  

  2. {  

  3.     BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init];  

  4.       

  5.     [demo setExecuteFinishedParam:^(BlockDemo * ademo) {  

  6.         if (ademo.resultCode == 200) {  

  7.             NSLog(@"call back ok.");  

  8.         }  

  9.     }];  

  10.       

  11.     [demo executeTest];  

  12. }  


不論是在外面init,仍是在裏面,且沒有加__block 及__weak。爲何,由於我我的經常在使用本身寫的block時,若是是回調,比較喜歡把自身看成參數傳到block中。這樣期實是編譯器給咱們作了弱引用。所以不會產生循環引用。

因爲我一直都這樣寫block,因此朋友一問起,我就說不會循環引用了,由於壓根他碰到的就是前面講述的哪一種訪問方式,而我回答的是個人這種使用方式。正由於口頭描述,與實際回覆真是差之千里。。。哈哈。爲了驗證我朋友的這個,我特地寫了個這篇文章,但願對你們有所幫助。最後,謝謝你們花時間閱讀。

相關文章
相關標籤/搜索