第三十五節(jié)—Block(二)

本文為L_Ares個人寫作茬故,以任何形式轉(zhuǎn)載請表明原文出處。

上一節(jié)重新的鞏固了一下Block的基礎(chǔ)知識和簡單的使用方式晦毙,以及解決循環(huán)引用的方法,本節(jié)則將通過clang編譯的文件和libclosure源碼去探索Block的一些更本質(zhì)的內(nèi)容立润。

準備

1. libclosure源碼

libclosure-73源碼文件

2. block的.cpp文件

1. 創(chuàng)建一個C語言Commond Line Tool項目霹琼。
圖0.2.0.png
2. 語言選擇C
圖0.2.1.png
3. 創(chuàng)建結(jié)果。
圖0.2.2.png
4. 在main.c中創(chuàng)建一個最簡單的Block對象首启,并且執(zhí)行Block
圖0.2.3.png
5. commond + B進行編譯撤摸,然后打開terminal終端毅桃,進行clang編譯成.cpp文件褒纲。
  • 先要進入到main.c所在的文件夾下
圖0.2.4.png
  • 然后打開terminal終端,輸入以下clang指令钥飞。
clang -rewrite-objc main.c -o main.cpp
6. clang結(jié)果
圖1.1.6.png

一莺掠、Block的clang探索

通過clang出來的.cpp,主要探索4點代承。

  1. Block的本質(zhì)是什么汁蝶。
  2. Block()的意義渐扮。
  3. Block捕獲外部變量的原理论悴。
  4. __block的原理。

1. Block的本質(zhì)

操作1 :

打開上面clang得到的main.cpp文件墓律“蚬溃滑至文件最后。

結(jié)果1 :

圖1.1.0.png

操作2 :

去掉(void(*)())(void *)的強制類型轉(zhuǎn)換耻讽,Block的結(jié)構(gòu)就變成了

結(jié)果2 :

圖1.1.1.png

操作3 :

commond + F搜索__main_block_impl_0察纯。

結(jié)果3 :

圖1.1.2.png

至此,可以看到Block塊的一個構(gòu)造方式针肥,其中的__block_impl結(jié)構(gòu)體存儲了Block塊的所有信息饼记。

操作4 :

commond + F搜索__block_impl

結(jié)果4 :

圖1.1.3.png

操作5 :

根據(jù)圖1.1.2圖1.1.3中慰枕,__block_impl結(jié)構(gòu)體的存儲屬性具则,以及官方給的注釋,進入libclosureBlock_private.h具帮。查找擁有這樣結(jié)構(gòu)的結(jié)構(gòu)體博肋。

結(jié)果5 :

圖1.1.4.png

結(jié)論 :

block的本質(zhì)是Block_layout結(jié)構(gòu)體。

2. Block()的意義

操作1 :

打開剛才的.cpp文件蜂厅,找到block的構(gòu)造函數(shù)那一行匪凡。

結(jié)果1 :

圖1.2.1.png

操作2 :

去掉block()經(jīng)過clang后,得到的那行代碼的所有強轉(zhuǎn)符號掘猿。

結(jié)果2 :

圖1.2.2.png

結(jié)論 :

1. 寫在block內(nèi)部的函數(shù)被保存到blockFuncPtr中病游,這僅僅只是函數(shù)的實現(xiàn)被保存進入block
2. block()才是真正的對函數(shù)進行調(diào)用稠通。

3. Block捕獲外部變量的原理

main.c中添加外部變量衬衬,更改main.c的代碼為 :

#include <stdio.h>

int main(int argc, const char * argv[]) {
    int a = 10;
    void(^block)(void) = ^{
        printf("a = %d",a);
    };
    block();
    return 0;
}

操作1 :

  1. 重新commond + B編譯main.c文件,然后進行clang轉(zhuǎn)換成.cpp文件采记。
  2. 打開新的.cpp文件佣耐,滑至文件最后。
  3. 去掉強轉(zhuǎn)唧龄。

結(jié)果1 :

圖1.3.0.png

操作2 :

再次查看block的構(gòu)造函數(shù)__main_block_impl_0

結(jié)果2 :

圖1.3.1.png

操作3 :

查看對外部變量a的調(diào)用兼砖。

結(jié)果3 :

圖1.3.2.png

結(jié)論 :

1. 當block捕獲外部變量的時候奸远,block結(jié)構(gòu)體內(nèi)部會生成一個相同類型、相同名稱的屬性讽挟。

2. 利用block自身的構(gòu)造函數(shù)懒叛,將外部變量傳入block結(jié)構(gòu)體內(nèi)部,用外部變量的值賦值給block內(nèi)部自動生成的同類型元素耽梅。

3. block內(nèi)部調(diào)用外部變量時薛窥,在fp函數(shù)體的內(nèi)部也會生成又一個對象,對外部變量進行值拷貝眼姐,這個對象和外部變量不是同一個對象诅迷。

4. 所以,當我們對block內(nèi)部捕獲的外部變量進行操作的時候众旗,是對另外一個對象進行操作罢杉,而不是對外部變量本身進行操作。

4. __block的原理

main.c中繼續(xù)修改代碼如下 :

#include <stdio.h>

int main(int argc, const char * argv[]) {
    __block int a = 10;
    void(^block)(void) = ^{
        printf("a = %d",a);
    };
    block();
    return 0;
}

操作1 :

  1. 重新commond + B編譯main.c文件贡歧,然后進行clang轉(zhuǎn)換成.cpp文件滩租。
  2. 打開新的.cpp文件,滑至文件最后利朵。
  3. 去掉強轉(zhuǎn)律想。

結(jié)果1 :

圖1.4.0.png

操作2 :

搜索__Block_byref_a_0結(jié)構(gòu)體。

結(jié)果2 :

圖1.4.1.png

操作3 :

查看圖1.4.0中的聲明blockblock的構(gòu)造函數(shù)绍弟,以及block()的實現(xiàn)

結(jié)果3 :

圖1.4.2.png

結(jié)論 :

1. __block會將外部變量變成一個結(jié)構(gòu)體對象A技即。

2. 結(jié)構(gòu)體對象A內(nèi)部的__forwarding存儲著指向外部變量的地址的指針。

3. block結(jié)構(gòu)體則在內(nèi)部生成新的晌柬、該類型的結(jié)構(gòu)體指針對象B姥份。

4. 通過構(gòu)造函數(shù),利用結(jié)構(gòu)體對象A的指針年碘,將結(jié)構(gòu)體對象A內(nèi)部的__forwarding賦值給結(jié)構(gòu)體指針對象B澈歉,也就是把指向外部變量的地址的指針賦值給結(jié)構(gòu)體指針對象B

5. 當調(diào)用外部變量的時候屿衅,block的函數(shù)內(nèi)部會生成一個該類型的結(jié)構(gòu)體指針對象C埃难,結(jié)構(gòu)體指針對象C通過結(jié)構(gòu)體指針對象B的賦值,擁有了指向外部變量的地址的指針涤久。

6. 所以在block內(nèi)部去改變被__block修飾的外部變量涡尘,實際上操作的是外部變量的地址上內(nèi)容。

7. __block是指針拷貝的實現(xiàn)响迂。

__block原理

二考抄、Block的內(nèi)存變化

上一節(jié)Block(一)中,已經(jīng)介紹了Block一共有6種類別蔗彤,而開發(fā)者常用的只有其中的3種川梅,分別是 :

1. NSGlobalBlock : 全局block
2. NSStackBlock : 棧block
3. NSMallocBlock : 堆block

1. NSGlobalBlock

先利用查看匯編疯兼,來查看NSGlobalBlock的聲明和創(chuàng)建流程。

步驟 :

1. 隨意創(chuàng)建一個項目贫途,在ViewControllerviewDidLoad中吧彪,簡單的創(chuàng)建一個最基本的block,在聲明block的地方掛上斷點丢早。

- (void)viewDidLoad {

    [super viewDidLoad];
    
    void(^block)(void) = ^{    //掛上斷點
        NSLog(@"this is a block");
    };
    block();

}

2. 打開xcode --> Debug --> Debug Workflow --> Always Show Disassembly姨裸,來看匯編。

圖2.1.0.png

3. 使用真機進行調(diào)試怨酝。運行項目傀缩。

圖2.1.1.png

4. 對objc_retainBlock加符號斷點,執(zhí)行到該斷點位置凫碌,查看objc_retainBlock有怎樣的調(diào)用扑毡。

圖2.1.2.png

走到這里就不用再往后走了,看匯編的最后一句 :

圖2.1.3.png

5. 此時讀取x0寄存器(這也是為什么要用真機的原因盛险,模擬器讀不到x0寄存器)。

圖2.1.4.png

結(jié)論 :

1. NSGlobalBlock類型的block對象勋又,經(jīng)過objc_retainBlock苦掘,調(diào)用了_Block_copy方法,最后將block對象完成創(chuàng)建并返回楔壤。

2. 當block沒有獲取外部變量的時候鹤啡,block是一個NSGlobalBlock類型的block對象。當block被在聲明全局變量的地方進行調(diào)用的時候蹲嚣,block也是NSGlobalBlock類型递瑰。

3. NSGlobalBlock存儲位置在內(nèi)存中的靜態(tài)區(qū)(.data區(qū))

2. NSStackBlock和NSMallocBlock

因為編譯器長期處在ARC環(huán)境下,所以這兩個一起說隙畜,因為不好捕捉NSStackBlock的瞬間抖部,ARC會自動的將NSStackBlock復制到堆中,變成NSMallocBlock议惰。

步驟 :

1. 對上面NSGlobalBlock的測試代碼做調(diào)整慎颗,增加一個外部變量,并在block內(nèi)部捕獲外部變量言询。

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    int a = 10;
    
    void(^block)(void) = ^{    //掛上斷點
        NSLog(@"%d",a);
    };
    block();
    
}

2. 刪除之前的所有符號斷點俯萎,在上述代碼注釋處掛上斷點,執(zhí)行項目

圖2.2.0.png

3. 在objc_retainBlock內(nèi)部查看此時的寄存器x0位置运杭,傳入的是什么類型的block夫啊。

圖2.2.1.png

4. 做一個objc_retainBlock符號斷點,進入objc_retainBlock辆憔。一直step into__Block_copy撇眯,并在__Block_copy的最后一句匯編谆趾,也就是那句返回,掛上斷點叛本。然后再讀寄存器x0

圖2.2.2.png

結(jié)論 :

1. 當block捕獲了外部變量之后沪蓬,block的類型就從NSGlobalBlock變成了NSStackBlock

2. NSStackBlock存在于__Block_copy完成之前来候。

3. __Block_copy內(nèi)部會將NSStackBlock變?yōu)?code>NSMallocBlock再返回跷叉。

4. NSStackBlockNSMallocBlock的地址是不一樣的,發(fā)生了一步copy的操作营搅。也就是說云挟,NSStackBlock通過copy可以得到NSMallocBlock

3. 為什么要對NSStackBlock進行copy

直接說結(jié)論

為了延長block的生命周期转质。

1. 如果block的作用域結(jié)束园欣,那么該block就會被廢棄,其內(nèi)部被__block修飾的外部變量也會隨之被廢棄休蟹。

2. 將block從棧區(qū)復制到堆區(qū)沸枯,即使存放在棧區(qū)的block已經(jīng)被廢棄,堆區(qū)的block依然可以使用赂弓,被__block修飾的外部變量也不會被廢棄绑榴。

圖2.3.0.png
圖2.3.1.png

三、Block的源碼探索

1. Block結(jié)構(gòu)體的解析

在上面我們已經(jīng)知道了Block的本質(zhì)是Block_layout結(jié)構(gòu)體盈魁,這里將對這個結(jié)構(gòu)體的屬性做一個介紹翔怎。

圖3.1.0.png

看圖3.1.0,block的結(jié)構(gòu)體指針中杨耙,可見5個結(jié)構(gòu)體的屬性赤套。

1.1 isa

這個isa,在.cpp的文件中珊膜,賦值的是block的類型容握,前文說過,block一共有6種辅搬,分別是 :

1. _NSConcreteStackBlock
2. _NSConcreteMallocBlock
3. _NSConcreteAutoBlock
4. _NSConcreteFinalizingBlock
5. _NSConcreteGlobalBlock
6. _NSConcreteWeakBlockVariable

而我們常用的block類型只屬于其中的3種 :

1. _NSConcreteGlobalBlock : 全局Block唯沮,對應NSGlobalBlock
2. _NSConcreteStackBlock : 棧Block堪遂,對應NSStackBlock介蛉。
3. _NSConcreteMallocBlock : 堆Block,對應NSMallocBlock溶褪。

1.2 flags

這是block的標識位币旧。

  • 類型是int32_t,表明它有32bit猿妈。
  • volatile修飾吹菱,為了保證多線程操作的時候巍虫,flags永遠都會從內(nèi)存中讀取真正的block標識數(shù)據(jù),而不是從CPU寄存器中讀取鳍刷,保證數(shù)據(jù)的正確性占遥。

flags會充分利用內(nèi)存,利用bit位存儲block的一些信息输瓜,類似isa中的ISA_BITFIELD利用位域存儲isa信息瓦胎。

flagsbit位存儲的內(nèi)容 :

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

第1位 : 存放著釋放標記。一般利用BLOCK_NEEDS_FREE(1左移24位)尤揣,做位與操作搔啊,再存入該位,記錄block是否需要釋放北戏。

第16位 : 存儲block的引用計數(shù)的值负芋。

第24位 : 是否需要釋放block。它會影響第1位的值嗜愈,也會影響第16位的值旧蛾。

第25位 : 是否擁有拷貝輔助函數(shù)。

第26位 : 是否擁有block析構(gòu)函數(shù)芝硬。

第27位 : 是否有垃圾回收蚜点。

第28位 : 是否是全局變量。

第30位 : block是否擁有一個簽名拌阴。如果沒有簽名,則第29位也不會被定義使用奶镶。

1.3 reserved

我也不知道干什么用的迟赃,如果有大佬知道的話,可以賜教厂镇,萬分感謝纤壁。

1.4 invoke

看其類型——BlockInvokeFunction

typedef void(*BlockInvokeFunction)(void *, ...);

重定義類型的函數(shù)指針,也就是存儲在block塊內(nèi)部的函數(shù)捺信。

1.5 descriptor

block的描述信息酌媒,是結(jié)構(gòu)體。擁有3種descriptor迄靠,其中秒咨,Block_descriptor_1block一定擁有的描述信息。

1.5.1 Block_descriptor_1

struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

存放了一個保留值和一個block的大小掌挚。

1.5.2 Block_descriptor_2

struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

存放了一個copy方法和一個dispose方法雨席。

官方注釋 : 當flags中的BLOCK_HAS_COPY_DISPOSE,也就是flags第25位為1的時候吠式,block就會擁有Block_descriptor_2描述陡厘。

1.5.3 Block_descriptor_3

struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

存放了block的簽名和layout抽米,layout依賴于flags的第31位是否為1。

官方注釋 : 當falgs中的BLOCK_HAS_SIGNATURE糙置,也就是flags的第30位為1的時候云茸,block就會擁有Block_descriptor_3

2. Block的簽名

OC對象都是有簽名的谤饭,既然說Block是OC對象标捺,那么Block也必然有簽名,Block簽名存儲在Block結(jié)構(gòu)體的Block_descriptor_3屬性中网持。

下面來驗證宜岛、并且獲得Block的簽名。

操作1 :

  1. 隨便創(chuàng)建一個項目功舀,并在ViewControllerviewDidLoad中創(chuàng)建一個block萍倡。
  2. 在下面代碼注釋處添加斷點。
  3. 打開匯編辟汰。xcode --> Debug --> Debug Workflow --> Always Show Disassembly
  4. 連接真機列敲,執(zhí)行代碼。
  5. lldb打印x0寄存器中的內(nèi)容帖汞。
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    void(^block)(void) = ^{    //掛上斷點
        NSLog(@"this is a block");
    };
    block();
    
}

結(jié)果1 :

圖3.2.0.png

這里已經(jīng)可以獲得Blocktype encoding了戴而,就是圖3.2.0中的signature : "v8@?0"。關(guān)于type encoding可以看我的這片博客關(guān)于Objective-C type encoding翩蘸。

其中v8表示void總共占用8位所意,@?表示一個不明類型的對象,0表示@?是在這個函數(shù)的第0位地址上催首。

那么扶踊,利用 :

[NSMethodSignature signatureWithObjCTypes:"@?"];

就可以獲得Block的簽名信息。

操作2 :

lldbpo [NSMethodSignature signatureWithObjCTypes:"@?"]

結(jié)果2 :

圖3.2.1.png

結(jié)論 :

1. Block擁有簽名郎任,并且存儲在Block_descriptor_3中秧耗。
2. Block的type encoding@?
3. Block的簽名是isObject,isBlock舶治。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末分井,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霉猛,更是在濱河造成了極大的恐慌尺锚,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件韩脏,死亡現(xiàn)場離奇詭異缩麸,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門杭朱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阅仔,“玉大人,你說我怎么就攤上這事弧械“司疲” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵刃唐,是天一觀的道長羞迷。 經(jīng)常有香客問我,道長画饥,這世上最難降的妖魔是什么衔瓮? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮抖甘,結(jié)果婚禮上热鞍,老公的妹妹穿的比我還像新娘。我一直安慰自己衔彻,他們只是感情好薇宠,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著艰额,像睡著了一般澄港。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柄沮,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天回梧,我揣著相機與錄音,去河邊找鬼祖搓。 笑死漂辐,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的棕硫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼袒啼,長吁一口氣:“原來是場噩夢啊……” “哼哈扮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚓再,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滑肉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后摘仅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體靶庙,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年娃属,在試婚紗的時候發(fā)現(xiàn)自己被綠了六荒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片护姆。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖掏击,靈堂內(nèi)的尸體忽然破棺而出卵皂,到底是詐尸還是另有隱情,我是刑警寧澤砚亭,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布灯变,位于F島的核電站,受9級特大地震影響捅膘,放射性物質(zhì)發(fā)生泄漏添祸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一寻仗、第九天 我趴在偏房一處隱蔽的房頂上張望刃泌。 院中可真熱鬧,春花似錦愧沟、人聲如沸蔬咬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽林艘。三九已至,卻和暖如春混坞,著一層夾襖步出監(jiān)牢的瞬間狐援,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工究孕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留啥酱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓厨诸,卻偏偏與公主長得像镶殷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子微酬,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

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

  • 1 Block機制 (Very Good) Block技巧與底層解析 http://www.reibang.com...
    Kevin_Junbaozi閱讀 4,052評論 3 48
  • 本文的內(nèi)容主要是基于Clang編譯器的官方文檔所寫绘趋。 在開始探索Block的本質(zhì)之前,大家先試著分析一下颗管,下面的代...
    無忘無往閱讀 846評論 0 2
  • 本文主要介紹block的類型陷遮、循環(huán)引用的解決方法以及block底層的分析 block 類型 block主要有三種類...
    北京_小海閱讀 622評論 0 2
  • 手動目錄循環(huán)引用block的類Block的相關(guān)信息block本質(zhì)block如何捕獲外界變量?__block修飾的本...
    Engandend閱讀 380評論 0 1
  • 一 Block的實現(xiàn) 1. 在main函數(shù)中聲明垦江、實現(xiàn)并調(diào)用一個block 2. 然后我們通過clang命令將ma...
    TIGER_XXXX閱讀 498評論 0 0