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

來源:互聯網
上載者:User

標籤:object-c   閉包   block   

fengsh998原文地址:http://blog.csdn.net/fengsh998/article/details/38090205轉載請註明出處如果覺得文章對你有所協助,請通過留言或關注公眾帳號fengsh998來支援我,謝謝!


本文不講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 <Foundation/Foundation.h>@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 和非ARC中進行編譯示範,所以我特意加了ARC先行編譯判斷。主要是方便不要改動太多的代碼來給大家示範。


在非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.

很顯然。儘管demo 是局部變數,並autorelease但可以看出自然至終並沒有得到釋放,這是因為block中使用了 block內進行訪問了自身的resultCode屬性。相信很多朋友也都會解決這種循環參考問題。就是在變數前面加個__block,就像這樣。

__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中的內容。大家可以運行一下看

輸出結果為:

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];}

這樣寫,即去除了警示又保證了block的運行。這才是我們最終想要的結果。
輸出為:

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];}


其實只是把init放到了類方法中進行書寫而已,但會有什麼不同。
+ (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但又想當然的朋友,有一些朋友喜歡去除警示,但只是盲目的加上__weak 或__block關鍵語,往往可能存在一些重大的安全隱患。就像示範中block根本不走。如果到了發布時,為了去警示而這樣簡單的處理了,並沒有進行測試就打包。哪麼將死得很慘。。。。。


好,到了尾聲,來說說為什麼朋友問我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];}

不管是在外面init,還是在裡面,且沒有加__block 及__weak。為什麼,因為我個人常常在使用自己寫的block時,如果是回調,比較喜歡把自身當作參數傳到block中。這樣期實是編譯器給我們做了弱引用。因此不會產生循環參考。

由於我一直都這樣寫block,所以朋友一問起,我就說不會循環參考了,因為壓根他碰到的就是前面講述的哪種訪問方式,而我回答的是我的這種使用方式。正因為口頭描述,與實際回複真是差之千裡。。。哈哈。為了驗證我朋友的這個,我特意寫了個這篇文章,希望對大家有所協助。最後,謝謝大家花時間閱讀。












聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.