Block的本質(zhì),揭開Block的神秘面紗

block在我們項(xiàng)目中經(jīng)常被使用到,它更輕型,使用起來更簡單,代碼更集中也連貫,方便閱讀.本文將從底層的角度,圍繞以下四點(diǎn),解析block的本質(zhì),讓你更加深刻了解block.

  1. block的原理是怎樣的?本質(zhì)是什么?
  2. __block的作用是什么?有什么注意點(diǎn)?
  3. block的屬性修飾詞為什么是copy?使用block有哪些注意?
  4. block在修改NSMutableArray時(shí),需不需要添加__block?

一: block的原理是怎樣的?本質(zhì)是什么?

  • block本質(zhì)上也是一個(gè)OC對象,因?yàn)樗膬?nèi)部也有個(gè)isa指針
  • block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象

接下來我們將通過底層源碼來論證上訴兩點(diǎn).
首先我們寫一個(gè)簡單的block,通過clang編譯器編譯成C++代碼,查看一下block的底層機(jī)構(gòu):

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        int height = 10;
        //聲明block變量
        void(^myBlock)(void) = ^{
            NSLog(@"age is %d height is %d",age,height);
        };
        //調(diào)用block
        myBlock();
    }
    return 0;
}

通過clang編譯器執(zhí)行編譯成C++代碼:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

clang編譯器編譯完后會(huì)得到一個(gè).cpp格式的文件,這就是我們剛才轉(zhuǎn)換的.m文件的底層代碼.我們打開.cpp文件,差不多有三萬多行代碼,我們直接拖到最下面,找到block相關(guān)的代碼.為了便于理解,我把block相關(guān)的代碼分為四個(gè)部分:

  • 一: block底層數(shù)據(jù)結(jié)構(gòu)
// 一: block底層數(shù)據(jù)結(jié)構(gòu)
struct __main_block_impl_0 {
    struct __block_impl impl; // 1: impl 結(jié)構(gòu)體
    struct __main_block_desc_0* Desc; // 2: block描述信息的結(jié)構(gòu)體
    int age; //3:捕獲的外部變量
    int height;
    //4: 和結(jié)構(gòu)體同名的構(gòu)造函數(shù) ( C++語法 , 類似于 OC 的init方法,返回一個(gè)結(jié)構(gòu)體對象,類似于返回self)
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int _height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

通過底層代碼我們可以看到,block在底層中的數(shù)據(jù)結(jié)構(gòu)是一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體有四個(gè)部分組成:
1: struct __block_impl
2: struct __main_block_desc_0
3: 捕獲的外部變量
4:和block結(jié)構(gòu)體同名的構(gòu)造函數(shù)

我們找到struct __block_impl 結(jié)構(gòu)體:

//struct __block_impl 結(jié)構(gòu)體
struct __block_impl {
  void *isa; //指向 block 的類型
  int Flags;//按位表示block的附加信息
  int Reserved;//保留變量
  void *FuncPtr; //封裝了執(zhí)行 block 代碼塊的函數(shù)地址
};

然后我們再找到struct __main_block_desc_0 結(jié)構(gòu)體 :

static struct __main_block_desc_0 {
  size_t reserved;//保留變量大小
  size_t Block_size;//block所占用的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

這3個(gè)結(jié)構(gòu)體之間的關(guān)系就像下圖這樣:


轉(zhuǎn)換關(guān)系

我們在網(wǎng)上看到的一張很經(jīng)典的block底層結(jié)構(gòu)的圖片就是把這兩個(gè)結(jié)構(gòu)體成員列表匯總進(jìn)去得到的:(descriptor結(jié)構(gòu)體中的copy 和 dispose 我們后面會(huì)講到)

網(wǎng)絡(luò)圖片
  • 二: main函數(shù)
// 1: main函數(shù)
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        int height = 10;
        //聲明block變量
        void(*myBlock)(int ,int) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, height));
        //調(diào)用block
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}

這段底層代碼中,聲明block和調(diào)用block的部分涉及了太多的類型轉(zhuǎn)換,不便于閱讀理解,我們?nèi)サ纛愋娃D(zhuǎn)換的部分,簡化如下:

// : main函數(shù)
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        int height = 10;
        //聲明block
void(*myBlock)(void) = &__main_block_impl_0(//調(diào)用block數(shù)據(jù)結(jié)構(gòu)中的同名的構(gòu)造函
                                __main_block_func_0,//封裝了block要執(zhí)行的代碼塊的函數(shù)
                                &__main_block_desc_0_DATA,//block描述信息的結(jié)構(gòu)體
                                age,//把局部變量當(dāng)做參數(shù)傳入
                                height
                                );
        //調(diào)用block
        myBlock->FuncPtr(myBlock);
    }
    return 0;
}

可以看到,我們在申明block的時(shí)候,調(diào)用block的構(gòu)造函數(shù),傳入了四個(gè)參數(shù),分別是__main_block_func_0 , __main_block_desc_0_DATA , age , height,我們對比著它的構(gòu)造函數(shù),看看它的內(nèi)部都做了什么:

  // block底層數(shù)據(jù)結(jié)構(gòu)
struct __main_block_impl_0 {
    struct __block_impl impl; // 1: impl 結(jié)構(gòu)體
    struct __main_block_desc_0* Desc; // 2: block描述信息的結(jié)構(gòu)體
    int age;//3: 捕獲的外部變量
    int height;
    //4: 同名的構(gòu)造函數(shù) ( C++語法 )
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int _height, int flags=0) : age(_age), height(_height)  {
    impl.isa = &_NSConcreteStackBlock; //isa 指向了block的類型
    impl.Flags = flags;//按位表示block的一些附加信息,這里是0
    impl.FuncPtr = fp; // 封裝了block要執(zhí)行的代碼塊的函數(shù)
    Desc = desc; //block描述信息的結(jié)構(gòu)體
  }
};
  • 首先,構(gòu)造函數(shù)的內(nèi)部會(huì)給impl結(jié)構(gòu)體的isa指針賦值,決定block的類型;
  • 然后,再把傳進(jìn)來的__main_block_func_0傳給了fp,又把fp賦值給了impl結(jié)構(gòu)體的FuncPtr,__main_block_func_0這個(gè)函數(shù)指針指向封裝了block代碼塊的函數(shù)地址;
  • 最后,再把傳進(jìn)來的__main_block_desc_0_DATA結(jié)構(gòu)體賦值給了Desc;
    其實(shí)這里還隱藏了一個(gè)很重要的一步,只是底層代碼上沒有體現(xiàn)出來:在構(gòu)造函數(shù)的內(nèi)部,會(huì)自動(dòng)把傳遞進(jìn)來_age,_height自動(dòng)賦值給我們block內(nèi)部的age,height,這就是block的捕獲機(jī)制.

我們來分別看一下__main_block_func_0,__main_block_desc_0_DATA這兩個(gè)結(jié)構(gòu)體長什么樣子:

  • __main_block_func_0 結(jié)構(gòu)體: block 真正要執(zhí)行的代碼塊
// 封裝了block要執(zhí)行的代碼塊的函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

 NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_b680e8_mi_0,a,b);

        }

可以看到,就是我們.m中寫的很簡單的一句NSLog語句.說明block的代碼塊確實(shí)是封裝成了一個(gè)函數(shù)去執(zhí)行的.

  • __main_block_desc_0_DATA:
static struct __main_block_desc_0 {
  size_t reserved;//保留變量的大小
  size_t Block_size;//block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

這個(gè)結(jié)構(gòu)體中主要就是一個(gè)size_t Block_size 存放了block的大小.

總結(jié):

  • block的底層就是一個(gè)struct _block_impl_0類型的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中包含一個(gè)isa指針,所以從更深的層次來說,block就是一個(gè)OC對象.
  • block結(jié)構(gòu)體中又包含implDesc結(jié)構(gòu)體,impl結(jié)構(gòu)體中有一個(gè)非常重要的成員FuncPtr,FuncPtr是一個(gè)指針,指向了封裝了blcok代碼塊的函數(shù),我們看到調(diào)用block的代碼:myBlock()的底層實(shí)際上是這樣子:myBlock->FuncPtr(myBlock);,可以看出調(diào)用block其實(shí)就是調(diào)用了FuncPtr()這個(gè)函數(shù)
  • Desc結(jié)構(gòu)體存放了block的大小

今天就先講一下block的底層數(shù)據(jù)結(jié)構(gòu)和本質(zhì),希望小伙伴們看完后能對block有一些更深刻的認(rèn)識,如有不對,歡迎指出.互相討論,共同進(jìn)步!
下一篇,我們講解block的變量捕獲機(jī)制,探究一下block是如何捕獲外部變量的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末她混,一起剝皮案震驚了整個(gè)濱河市坤按,隨后出現(xiàn)的幾起案子酗钞,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)郁季,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門盖淡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褪迟,“玉大人掀抹,你說我怎么就攤上這事蓉驹。” “怎么了瞻润?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵楚午,是天一觀的道長。 經(jīng)常有香客問我怪蔑,道長虹统,這世上最難降的妖魔是什么渡冻? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任超歌,我火速辦了婚禮握础,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘定枷。我一直安慰自己覆旭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布腕铸。 她就那樣靜靜地躺著汽烦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颂砸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天金抡,我揣著相機(jī)與錄音巫击,去河邊找鬼顷级。 笑死,一個(gè)胖子當(dāng)著我的面吹牛付呕,可吹牛的內(nèi)容都是我干的姆钉。 我是一名探鬼主播毯辅,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼媚媒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坪圾?” 一聲冷哼從身側(cè)響起病梢,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤隧期,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忆植。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拾氓。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖湿颅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搀玖,我是刑警寧澤即舌,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布耀石,位于F島的核電站梆奈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一场仲、第九天 我趴在偏房一處隱蔽的房頂上張望馍忽。 院中可真熱鬧坐梯,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽募书。三九已至齿椅,卻和暖如春遣蚀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工柏卤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓唤崭,卻偏偏與公主長得像冕杠,于是被迫代替她去往敵國和親魁淳。 傳聞我的和親對象是個(gè)殘疾皇子息拜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

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

  • 1 Block機(jī)制 (Very Good) Block技巧與底層解析 http://www.reibang.com...
    Kevin_Junbaozi閱讀 4,038評論 3 48
  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動(dòng)變量值 int main(){ ...
    南京小伙閱讀 913評論 1 3
  • 前言 Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,759評論 0 23
  • 摘要block是2010年WWDC蘋果為Objective-C提供的一個(gè)新特性孝情,它為我們開發(fā)提供了便利间唉,比如GCD...
    西門吹雪123閱讀 906評論 0 4
  • 念昨悼,是唇齒纏綿,讓心柔軟 念,是嫩芽破土击狮,萌生希望 念,是縈繞心間,一切快樂和痛苦的源泉 念,是親密聯(lián)結(jié),一世為人...
    Yaqin瑜伽閱讀 274評論 0 0