深入研究Block

摘選自:[http://www.reibang.com/p/ee9756f3d5f6]

Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這個(gè)新功能“Blocks”快耿。從那開始囊陡,Block就出現(xiàn)在iOS和Mac系統(tǒng)各個(gè)API中,并被大家廣泛使用掀亥。一句話來形容Blocks撞反,帶有自動(dòng)變量(局部變量)的匿名函數(shù)。

Block在OC中的實(shí)現(xiàn)如下:

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
block結(jié)構(gòu)圖.png

從結(jié)構(gòu)圖中很容易看到isa搪花,所以O(shè)C處理Block是按照對(duì)象來處理的遏片。在iOS中,isa常見的就是_NSConcreteStackBlock撮竿,_NSConcreteMallocBlock丁稀,_NSConcreteGlobalBlock這3種(另外只在GC環(huán)境下還有3種使用的_NSConcreteFinalizingBlock,_NSConcreteAutoBlock倚聚,_NSConcreteWeakBlockVariable线衫,本文暫不談?wù)撨@3種,有興趣的看看官方文檔)

1.從捕獲外部變量的角度上來看

_NSConcreteStackBlock:
只用到外部局部變量惑折、成員屬性變量授账,且沒有強(qiáng)指針引用的block都是StackBlock。
StackBlock的生命周期由系統(tǒng)控制的惨驶,一旦返回之后白热,就被系統(tǒng)銷毀了。

_NSConcreteMallocBlock:
有強(qiáng)指針引用或copy修飾的成員屬性引用的block會(huì)被復(fù)制一份到堆中成為MallocBlock粗卜,沒有強(qiáng)指針引用即銷毀屋确,生命周期由程序員控制

_NSConcreteGlobalBlock:
沒有用到外界變量或只用到全局變量、靜態(tài)變量的block為_NSConcreteGlobalBlock,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束攻臀。

2.從持有對(duì)象的角度上來看:

  • _NSConcreteStackBlock是不持有對(duì)象的焕数。
//以下是在MRC下執(zhí)行的
    NSObject * obj = [[NSObject alloc]init];
    NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);

    void (^myBlock)(void) = ^{
        NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
    };

    NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);

    myBlock();

輸出:

1.Block外 obj = 1
2.Block外 obj = 1
Block中 obj = 1
  • _NSConcreteMallocBlock是持有對(duì)象的。
//以下是在MRC下執(zhí)行的
    NSObject * obj = [[NSObject alloc]init];
    NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);

    void (^myBlock)(void) = [^{
        NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
    }copy];

    NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);

    myBlock();

    [myBlock release];

    NSLog(@"3.Block外 obj = %lu",(unsigned long)obj.retainCount);

輸出:

1.Block外 obj = 1
2.Block外 obj = 2
Block中 obj = 2
3.Block外 obj = 1
  • _NSConcreteGlobalBlock也不持有對(duì)象
//以下是在MRC下執(zhí)行的
    void (^myBlock)(void) = ^{

        NSObject * obj = [[NSObject alloc]init];
        NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
    };

    myBlock();

輸出:

Block 中 obj = 1

由于_NSConcreteStackBlock所屬的變量域一旦結(jié)束刨啸,那么該Block就會(huì)被銷毀堡赔。在ARC環(huán)境下,編譯器會(huì)自動(dòng)的判斷设联,把Block自動(dòng)的從棧copy到堆善已。比如當(dāng)Block作為函數(shù)返回值的時(shí)候,肯定會(huì)copy到堆上离例。

1.手動(dòng)調(diào)用copy
2.Block是函數(shù)的返回值
3.Block被強(qiáng)引用换团,Block被賦值給__strong或者id類型
4.調(diào)用系統(tǒng)API入?yún)⒅泻衭singBlcok的方法

以上4種情況,系統(tǒng)都會(huì)默認(rèn)調(diào)用copy方法把Block賦復(fù)制

但是當(dāng)Block為函數(shù)參數(shù)的時(shí)候宫蛆,就需要我們手動(dòng)的copy一份到堆上了啥寇。這里除去系統(tǒng)的API我們不需要管,比如GCD等方法中本身帶usingBlock的方法洒扎,其他我們自定義的方法傳遞Block為參數(shù)的時(shí)候都需要手動(dòng)copy一份到堆上辑甜。

copy函數(shù)把Block從棧上拷貝到堆上,dispose函數(shù)是把堆上的函數(shù)在廢棄的時(shí)候銷毀掉袍冷。

Block中__block實(shí)現(xiàn)原理

1.普通非對(duì)象的變量

  __block的變量也被轉(zhuǎn)化成了一個(gè)結(jié)構(gòu)體

2.對(duì)象的變量
在MRC環(huán)境下磷醋,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制胡诗。
而在ARC環(huán)境下邓线,對(duì)于聲明為__block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain煌恢,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象骇陈,所以才會(huì)產(chǎn)生循環(huán)引用的問題!

最后

關(guān)于Block捕獲外部變量有很多用途瑰抵,用途也很廣你雌,只有弄清了捕獲變量和持有的變量的概念以后,之后才能清楚的解決Block循環(huán)引用的問題二汛。

再次回到文章開頭婿崭,5種變量,自動(dòng)變量肴颊,函數(shù)參數(shù) 氓栈,靜態(tài)變量,靜態(tài)全局變量婿着,全局變量授瘦,如果嚴(yán)格的來說醋界,捕獲是必須在Block結(jié)構(gòu)體__main_block_impl_0里面有成員變量的話,Block能捕獲的變量就只有帶有自動(dòng)變量和靜態(tài)變量了提完。捕獲進(jìn)Block的對(duì)象會(huì)被Block持有形纺。

對(duì)于非對(duì)象的變量來說,

自動(dòng)變量的值氯葬,被copy進(jìn)了Block挡篓,不帶__block的自動(dòng)變量只能在里面被訪問婉陷,并不能改變值帚称。

圖片.png

帶__block的自動(dòng)變量 和 靜態(tài)變量 就是直接地址訪問。所以在Block里面可以直接改變變量的值秽澳。

圖片.png

而剩下的靜態(tài)全局變量闯睹,全局變量,函數(shù)參數(shù)担神,也是可以在直接在Block中改變變量值的楼吃,但是他們并沒有變成Block結(jié)構(gòu)體__main_block_impl_0的成員變量,因?yàn)樗麄兊淖饔糜虼笸叮钥梢灾苯痈乃麄兊闹怠?/p>

值得注意的是孩锡,靜態(tài)全局變量,全局變量亥贸,函數(shù)參數(shù)他們并不會(huì)被Block持有躬窜,也就是說不會(huì)增加retainCount值。

對(duì)于對(duì)象來說炕置,

在MRC環(huán)境下荣挨,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制朴摊。
而在ARC環(huán)境下默垄,對(duì)于聲明為__block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain甚纲,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象口锭。

ARC環(huán)境下,Block也是存在__NSStackBlock的時(shí)候的

在ARC環(huán)境下介杆,Block也是存在__NSStackBlock的時(shí)候的讹弯,平時(shí)見到最多的是_NSConcreteMallocBlock,是因?yàn)槲覀儠?huì)對(duì)Block有賦值操作这溅,所以ARC下组民,block 類型通過=進(jìn)行傳遞時(shí),會(huì)導(dǎo)致調(diào)用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈悲靴。并導(dǎo)致 NSStackBlock 類型的 block 轉(zhuǎn)換為 NSMallocBlock 類型臭胜。

舉例如下:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {

    __block int temp = 10;

    NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});

    return 0;
}

輸出

<__NSStackBlock__: 0x7fff5fbff768>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莫其,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耸三,更是在濱河造成了極大的恐慌乱陡,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仪壮,死亡現(xiàn)場(chǎng)離奇詭異憨颠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)积锅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門爽彤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缚陷,你說我怎么就攤上這事适篙。” “怎么了箫爷?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵嚷节,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我虎锚,道長(zhǎng)硫痰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任窜护,我火速辦了婚禮效斑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柄慰。我一直安慰自己鳍悠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布坐搔。 她就那樣靜靜地躺著藏研,像睡著了一般。 火紅的嫁衣襯著肌膚如雪概行。 梳的紋絲不亂的頭發(fā)上蠢挡,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音凳忙,去河邊找鬼业踏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涧卵,可吹牛的內(nèi)容都是我干的勤家。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柳恐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼伐脖!你這毒婦竟也來了热幔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤讼庇,失蹤者是張志新(化名)和其女友劉穎绎巨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蠕啄,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡场勤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歼跟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片和媳。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嘹承,靈堂內(nèi)的尸體忽然破棺而出窗价,到底是詐尸還是另有隱情如庭,我是刑警寧澤叹卷,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站坪它,受9級(jí)特大地震影響骤竹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜往毡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一蒙揣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧开瞭,春花似錦懒震、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至葱色,卻和暖如春递宅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苍狰。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工办龄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人淋昭。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓俐填,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親翔忽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子英融,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容