iOS中block介紹(三)揭開神秘面紗(上)
block到底是什么
我們使用clang的rewrite-objc命令來獲取轉(zhuǎn)碼后的代碼。
1、block的底層實現(xiàn)
我們來看看最簡單的一個block:
這個block僅僅打印棧變量i和j的值,其被clang轉(zhuǎn)碼為:
首先是一個結(jié)構(gòu)體__main_block_impl_0(從圖二中的***一行可以看到,block是一個指向__main_block_impl_0的指針,初始化后被類型強轉(zhuǎn)為函數(shù)指針),其中包含的__block_impl是一個公共實現(xiàn)(學過c語言的同學都知道,__main_block_impl_0的這種寫法表示其可以被類型強轉(zhuǎn)為__block_impl類型):
- struct __block_impl {
 - void *isa;
 - int Flags;
 - int Reserved;
 - void *FuncPtr;
 - };
 
isa指針說明block可以成為一個objc對象。
__main_block_impl_0的意思是main函數(shù)中的第0個block的implementation,這就是這個block的主體了。
這個結(jié)構(gòu)體的構(gòu)造函數(shù)的參數(shù):
block實際執(zhí)行代碼所在的函數(shù)的指針,當block真正被執(zhí)行時,實際上是調(diào)用了這個函數(shù),其命名也是類似的方式。
block的描述結(jié)構(gòu)體,注意這個結(jié)構(gòu)體聲明結(jié)束時就創(chuàng)建了一個唯一的desc,這個desc包含了block的大小,以及復制和析構(gòu)block時需要額外調(diào)用的函數(shù)。
接下來是block所引用到的變量們
***是一個標記值,內(nèi)部實現(xiàn)需要用到的。(我用計算器看了一下,570425344這個值等于1<<29,即BLOCK_HAS_DESCRIPTOR這個枚舉值)
所以,我們可以看到:
為什么上一篇我們說j已經(jīng)不是原來的j了,因為j是作為參數(shù)傳入了block的構(gòu)造函數(shù),進行了值復制。
帶有__block標記的變量會被取地址來傳入構(gòu)造函數(shù),為修改其值奠定了基礎
接下來是block執(zhí)行函數(shù)__main_block_func_0:
其唯一的參數(shù)是__main_block_impl_0的指針,我們看到printf語句的數(shù)據(jù)來源都取自__cself這個指針,比較有意思的是i的取值方式(帶有__block標記的變量i被轉(zhuǎn)碼為一個結(jié)構(gòu)體),先取__forward指針,再取i,這為將i復制到堆中奠定了基礎。
再下來是預定義好的兩個復制/釋放輔助函數(shù),其作用后面會講到。
***是block的描述信息結(jié)構(gòu)體 __main_block_desc_0,其包含block的內(nèi)存占用長度,已經(jīng)復制/釋放輔助函數(shù)的指針,其聲明結(jié)束時,就創(chuàng)建了一個名為__main_block_desc_0_DATA的結(jié)構(gòu)體,我們看它構(gòu)造時傳入的值,這個DATA結(jié)構(gòu)體的作用就一目了然了:
長度用sizeof計算,輔助函數(shù)的指針分別為上面預定義的兩個輔助函數(shù)。
注意,如果這個block沒有使用到需要在block復制時進行copy/retian的變量,那么desc中不會有輔助函數(shù)
至此,一個block所有的部件我們都看齊全了,一個主體,一個真正的執(zhí)行代碼函數(shù),一個描述信息(可能包含兩個輔助函數(shù))。
2、構(gòu)造一個block
我們進入main函數(shù):
圖一中的第三行(block的聲明),在圖二中,轉(zhuǎn)化為一個函數(shù)指針的聲明,并且都沒有被賦予初始值。
而圖一中的***一行(創(chuàng)建一個block),在圖二中,成為了對__main_block_impl_0的構(gòu)造函數(shù)的調(diào)用,傳入的參數(shù)的意義上面我們已經(jīng)講過了。
所以構(gòu)造一個block就是創(chuàng)建了__main_block_impl_0 這個c++類的實例。
3、調(diào)用一個block
調(diào)用一個block的寫法很簡單,與調(diào)用c語言函數(shù)的語法一樣:
- blk();
 
其轉(zhuǎn)碼后的語句:
- ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
 
將blk這個函數(shù)指針類型強轉(zhuǎn)為__block_impl類型,然后取其執(zhí)行函數(shù)指針,然后將此指針類型強轉(zhuǎn)為返回void*并接收一個__block_impl*的函數(shù)指針,***調(diào)用這個函數(shù),傳入強轉(zhuǎn)為__block_impl*類型的blk,
即調(diào)用了前述的函數(shù)__main_block_func_0
4、objective-c類成員函數(shù)中的block
源碼如下:
- - (void)of1
 - {
 - OBJ1* oj = self;
 - void (^oblk)(void) = ^{ printf("%d\n", oj.oi);};
 - Block_copy(oblk);
 - }
 
這里我故意將self賦值給oj這個變量,是為了驗證前一章提出的一個結(jié)論:無法通過簡單的間接引用self來防止retain循環(huán),要避免循環(huán),我們需要__block標記(多謝樓下網(wǎng)友的提醒)
轉(zhuǎn)碼如下:
- struct __OBJ1__of1_block_impl_0 {
 - struct __block_impl impl;
 - struct __OBJ1__of1_block_desc_0* Desc;
 - OBJ1 *oj;
 - __OBJ1__of1_block_impl_0(void *fp, struct __OBJ1__of1_block_desc_0 *desc, OBJ1 *_oj, int flags=0) : oj(_oj) {
 - impl.isa = &_NSConcreteStackBlock;
 - impl.Flags = flags;
 - impl.FuncPtr = fp;
 - Desc = desc;
 - }
 - };
 - static void __OBJ1__of1_block_func_0(struct __OBJ1__of1_block_impl_0 *__cself) {
 - OBJ1 *oj = __cself->oj; // bound by copy
 - printf("%d\n", ((int (*)(id, SEL))(void *)objc_msgSend)((id)oj, sel_registerName("oi")));}
 
objc方法中的block與c中的block并無太多差別,只是一些標記值可能不同,為了標記其是objc方法中的blcok。
注意其構(gòu)造函數(shù)的參數(shù):OBJ1 *_oj
這個_oj在block復制到heap時,會被retain,而_oj與self根本就是相等的,所以,最終retain的就是self,所以如果當前實例持有了這個block,retain循環(huán)就形成了。
而一旦為其增加了__block標記:
- - (void)of1
 - {
 - __block OBJ1 *bSelf = self;
 - ^{ printf("%d", bSelf.oi); };
 - }其轉(zhuǎn)碼則變?yōu)椋?nbsp;
 - //增加了如下行
 - struct __Block_byref_bSelf_0 {
 - void *__isa;
 - __Block_byref_bSelf_0 *__forwarding;
 - int __flags;
 - int __size;
 - void (*__Block_byref_id_object_copy)(void*, void*);
 - void (*__Block_byref_id_object_dispose)(void*);
 - OBJ1 *bSelf;
 - };
 - static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 - _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
 - }
 - static void __Block_byref_id_object_dispose_131(void *src) {
 - _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
 - }
 - //聲明處變?yōu)?/span>
 - __block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
 
clang為我們的bSelf結(jié)構(gòu)體創(chuàng)建了自己的copy/dispose輔助函數(shù),33554432(即1<<25 BLOCK_HAS_COPY_DISPOSE)這個值告訴系統(tǒng),我們的bSelf結(jié)構(gòu)體具有copy/dispose輔助函數(shù)。
而131這個參數(shù)(二進制1000 0011,即BLOCK_FIELD_IS_OBJECT (3) |BLOCK_BYREF_CALLER(128))
中的BLOCK_BYREF_CALLER在內(nèi)部實現(xiàn)中告訴系統(tǒng)不要進行retain或者copy,
也就是說,在 __block bSelf 被復制至heap上時,系統(tǒng)會發(fā)現(xiàn)有輔助函數(shù),而輔助函數(shù)調(diào)用后,并不retain或者copy 其結(jié)構(gòu)體內(nèi)的bSelf。
這樣就避免了循環(huán)retain。
小結(jié):
當我們創(chuàng)建一個block,并調(diào)用之,編譯器為我們做的事情如下:
1.創(chuàng)建block所有的部件代碼:一個主體,一個真正的執(zhí)行代碼函數(shù),一個描述信息(可能包含兩個輔助函數(shù))。
2.將我們的創(chuàng)建代碼轉(zhuǎn)碼為block_impl的構(gòu)造語句。
3.將我們的執(zhí)行語句轉(zhuǎn)碼為對block的執(zhí)行函數(shù)的調(diào)用。
下一篇我們將剖析runtime.c的源碼,并理解block的堆棧轉(zhuǎn)換。
















 
 
 


 
 
 
 