摘要
block是2010年WWDC蘋果為Objective-C提供的一個(gè)新特性府瞄,它為我們開發(fā)提供了便利,比如GCD就大量使用了block哪审,用來往執(zhí)行隊(duì)列中添加執(zhí)行娶牌。那么block到底是什么東西呢。其實(shí)它就是一個(gè)閉包摆屯,一個(gè)引用自動(dòng)變量的函數(shù)邻遏。很多語言也實(shí)現(xiàn)自己的閉包,比如C#的lamda表達(dá)式虐骑。這篇文章將從分析源碼的角度來分析下block到底是什么鬼准验。
最簡單的block,不持有變量
我們先新建一個(gè)源文件:block.c 代碼如下
include <stdio.h>int main(){ void (^blk)(void) = ^(){printf("This is a block.");}; blk(); return 0;}
我們使用clang(LLVM編譯器廷没,和GCC類似)糊饱,通過命令clang -rewrite-objc block.c
,解析block.c這樣我們就會(huì)得到對應(yīng)的cpp文件block.cpp颠黎。去除一些影響我們閱讀的代碼另锋。如下:
struct __block_impl { void *isa; int Flags; int Reserved; void FuncPtr;};struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 __cself) { printf("This is a block.");}static struct __main_block_desc_0 { size_t reserved; size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(){ void (blk)(void) = (void ()())&__main_block_impl_0((void )__main_block_func_0 ,&__main_block_desc_0_DATA); ((void ()(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0;}
下面我們來分析下源碼,看看我們定義的block到底是個(gè)什么東西盏缤。先看下main()函數(shù)砰蠢,我們定義了block
void (^blk)(void) = ^(){printf("This is a block.");};
被轉(zhuǎn)化成了
void (blk)(void) = (void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
去除影響閱讀的強(qiáng)制轉(zhuǎn)換代碼后
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
這樣就清晰了。我們寫的block被轉(zhuǎn)化成了指向__main_block_impl_0
結(jié)構(gòu)體的指針唉铜。構(gòu)造函數(shù)的參數(shù)我們先不管台舱,慢慢一步步分析首先,我們來看下第一個(gè)struct
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};
isa指針潭流,如果我們對runtime了解的話竞惋,就明白isa指向Class的指針。
Flags灰嫉,當(dāng)block被copy時(shí)拆宛,應(yīng)該執(zhí)行的操作
Reserved為保留字段
FuncPtr指針,指向block內(nèi)的函數(shù)實(shí)現(xiàn)
__block_impl
保存block的類型isa(如&_NSConcreteStackBlock)讼撒,標(biāo)識(shí)(當(dāng)block發(fā)生copy時(shí)浑厚,會(huì)用到)股耽,block的方法。方法實(shí)現(xiàn)如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("This is a block.");}
下面我們再看一個(gè)結(jié)構(gòu)體
static struct __main_block_desc_0 { size_t reserved; size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
reserved為保留字段默認(rèn)為0
Block_size為sizeof(struct __main_block_impl_0)
钳幅,用來表示block所占內(nèi)存大小物蝙。因?yàn)闆]有持有變量,block大小為impl的大小加上Desc指針大小
__main_block_desc_0_DATA
為__main_block_desc_0的一個(gè)結(jié)構(gòu)體實(shí)例這個(gè)結(jié)構(gòu)體敢艰,用來描述block的大小等信息诬乞。如果持有可修改的捕獲變量時(shí)(即加__block
),會(huì)增加兩個(gè)函數(shù)(copy和dispose)钠导,我們后面會(huì)分析
再看最重要的一個(gè)結(jié)構(gòu)體__main_block_impl_0
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};
__main_block_impl_0
里面有兩個(gè)變量struct __block_impl impl
和struct __main_block_desc_0
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc;}
結(jié)構(gòu)體構(gòu)造函數(shù)用來初始化變量__main_block_impl_0
和__main_block_desc_0
注:clang轉(zhuǎn)換的代碼和真實(shí)運(yùn)行時(shí)有區(qū)別震嫉。應(yīng)該為impl.isa = &_NSConcreteGlobalBlock
我們再來看下最開始的
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
我們可以看到,block其實(shí)就是指向__main_block_impl_0
的結(jié)構(gòu)體指針牡属,這個(gè)結(jié)構(gòu)體包含兩個(gè)__block_impl
和__main_block_desc_0
兩個(gè)結(jié)構(gòu)體票堵,和一個(gè)方法。通過上面的分析湃望,是不是很已經(jīng)清晰了最后换衬,main函數(shù)里面的
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
同樣,去除轉(zhuǎn)化代碼证芭,上面的代碼就可以轉(zhuǎn)化為
blk->FuncPtr(blk);
執(zhí)行block函數(shù)
這樣我們就完成了瞳浦,對簡單block實(shí)現(xiàn)的分析。是不是很簡單
持有變量的block
我們知道block可以持有變量废士,現(xiàn)在我們實(shí)現(xiàn)一個(gè)持有變量的block叫潦。修改下原來的block.c源文件
include <stdio.h>int main(){ int i = 4; void (^blk)(void) = ^(){printf("i = %d", i);}; i++; blk(); return 0;}
同樣的,使用clang命令轉(zhuǎn)化下上述代碼
struct __block_impl { void *isa; int Flags; int Reserved; void FuncPtr;};struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; int i; __main_block_impl_0(void *fp, struct __main_block_desc_0 desc, int _i, int flags=0) : i(_i) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 __cself) { int i = __cself->i; // bound by copy printf("i=%d", i);}static struct __main_block_desc_0 { size_t reserved; size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(){ int i = 4; void (blk)(void) = (void ()())&__main_block_impl_0((void )__main_block_func_0, &__main_block_desc_0_DATA, i); ((void ()(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); i++; return 0;}
我們只看下在持有變量時(shí)官硝,block轉(zhuǎn)化矗蕊,有哪些不同
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int i; /看這里~看這里~/ __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};
__main_block_impl_0
結(jié)構(gòu)體多了一個(gè)變量i。這個(gè)變量用來保存main函數(shù)的變量i氢架。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int i = __cself->i; // bound by copy printf("i=%d", i);}
在執(zhí)行block時(shí)傻咖,取出的i為__main_block_impl_0保存的值,這兩個(gè)變量不是同一個(gè)岖研。這就是為什么我們執(zhí)行了i++操作卿操,再執(zhí)行block,i的值仍然不變的原因
可修改持有變量的block
為了修改持有變量孙援,我們在變量前面加上__block
害淤,修改后的block.c如下
include <stdio.h>int main(){ __block int i = 4; void (^blk)(void) = ^(){printf("i = %d", i);}; i++; blk(); return 0;}
轉(zhuǎn)化后的代碼如下
struct __block_impl { void isa; int Flags; int Reserved; void FuncPtr;};struct __Block_byref_i_0 { void __isa; __Block_byref_i_0 __forwarding; int __flags; int __size; int i;};struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; __Block_byref_i_0 i; // by ref __main_block_impl_0(void fp, struct __main_block_desc_0 desc, __Block_byref_i_0 _i, int flags=0) : i(_i->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 __cself) { __Block_byref_i_0 i = __cself->i; // bound by ref (i->__forwarding->i)++; printf("i=%d", (i->__forwarding->i));}static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) { _Block_object_assign((void)&dst->i, (void)src->i, 8/BLOCK_FIELD_IS_BYREF/);}static void __main_block_dispose_0(struct __main_block_impl_0src) { _Block_object_dispose((void)src->i, 8/BLOCK_FIELD_IS_BYREF/);}static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (copy)(struct __main_block_impl_0, struct __main_block_impl_0); void (dispose)(struct __main_block_impl_0);} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};int main(){ attribute((blocks(byref))) __Block_byref_i_0 i = {(void)0,(__Block_byref_i_0 )&i, 0, sizeof(__Block_byref_i_0), 4}; void (blk)(void) = (void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 )&i, 570425344); ((void ()(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0;}
我們發(fā)現(xiàn)當(dāng)我們想要修改持有變量時(shí),轉(zhuǎn)化后的代碼有所增加拓售。當(dāng)我們在變量前面加上__block
時(shí)窥摄,就會(huì)生成一個(gè)結(jié)構(gòu)體,來保存變量值础淤。新增了結(jié)構(gòu)體__Block_byref_i_0
崭放,實(shí)現(xiàn)如下
struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i;};
__isa指向變量Class
____forwarding哨苛,指向自己的指針,當(dāng)從棧copy到堆時(shí)莹菱,指向堆上的block
__flags移国,當(dāng)block被copy時(shí)吱瘩,標(biāo)識(shí)被捕獲的對象道伟,該執(zhí)行的操作
__size,結(jié)構(gòu)體大小
i使碾,持有的變量
看下轉(zhuǎn)換后的main函數(shù)
attribute((blocks(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 4};
即Block_byref_i_0 i = {(void)0,&i, 0, sizeof(*Block_byref_i_0), 4};int i = 4
被轉(zhuǎn)化成上述代碼蜜徽。它被轉(zhuǎn)化成結(jié)構(gòu)體__Block_byref_i_0
。__Block_byref_i_0
持有變量i票摇。
i++;blk();
也轉(zhuǎn)化成對__Block_byref_i_0
中的變量i進(jìn)行++運(yùn)算
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_i_0 *i = __cself->i; // bound by ref (i->__forwarding->i)++; printf("i=%d", (i->__forwarding->i));}
這樣便達(dá)到對i值的修改
Block_copy(...)的實(shí)現(xiàn)
根據(jù)Block.h上顯示拘鞋,Block_copy(...)被定義如下:
define Block_copy(...) ((__typeof(VA_ARGS))_Block_copy((const void *)(VA_ARGS)))
_Block_copy
被聲明在runtime.c中,對應(yīng)實(shí)現(xiàn):
void *_Block_copy(const void *arg) { return _Block_copy_internal(arg, WANTS_ONE);}
該方法調(diào)用了
/* Copy, or bump refcount, of a block. If really copying, call the copy helper if present. */static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; ... if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } // Its a stack block. Make a copy. struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return (void )0; memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; result->isa = _NSConcreteMallocBlock; if (result->flags & BLOCK_HAS_COPY_DISPOSE) { //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock); (aBlock->descriptor->copy)(result, aBlock); // do fixup return result; }}
當(dāng)原始block在堆上時(shí)矢门,引用計(jì)數(shù)+1盆色。當(dāng)為全局block時(shí),copy不做任何操作
// Its a stack block. Make a copy.struct Block_layout *result = malloc(aBlock->descriptor->size);if (!result) return (void )0;memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first// reset refcountresult->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not neededresult->flags |= BLOCK_NEEDS_FREE | 1;result->isa = _NSConcreteMallocBlock;if (result->flags & BLOCK_HAS_COPY_DISPOSE) { //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock); (aBlock->descriptor->copy)(result, aBlock); // do fixup}
當(dāng)block在棧上時(shí)祟剔,調(diào)用Block_copy隔躲,block將被copy到堆上。如果block實(shí)現(xiàn)了copy和dispose方法物延,則調(diào)用對應(yīng)的方法宣旱,來處理捕獲的變量。
小節(jié)
通過上面的分析叛薯,相信大家對block有了更加清晰的理解浑吟。??如果大家有興趣,可以看下block在runtime的源碼耗溜,結(jié)合我們上面轉(zhuǎn)換的c++代碼组力,可以看到更完整實(shí)現(xiàn)細(xì)節(jié)。下節(jié)抖拴,我將從使用block所引發(fā)的retain cycle問題燎字,來分析runtime的源碼。