淺談Block原理

摘要
block是2010年WWDC蘋果為Objective-C提供的一個新特性,它為我們開發(fā)提供了便利,比如GCD就大量使用了block,用來往執(zhí)行隊列中添加執(zhí)行。那么block到底是什么東西呢。其實它就是一個閉包,一個引用自動變量的函數(shù)。很多語言也實現(xiàn)自己的閉包,比如C#的lamda表達式。這篇文章將從分析源碼的角度來分析下block到底是什么鬼。
最簡單的block,不持有變量
我們先新建一個源文件: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這樣我們就會得到對應(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到底是個什么東西。先看下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);

去除影響閱讀的強制轉(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ù)我們先不管,慢慢一步步分析首先,我們來看下第一個struct
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};

isa指針,如果我們對runtime了解的話,就明白isa指向Class的指針。
Flags,當block被copy時,應(yīng)該執(zhí)行的操作
Reserved為保留字段
FuncPtr指針,指向block內(nèi)的函數(shù)實現(xiàn)

__block_impl
保存block的類型isa(如&_NSConcreteStackBlock),標識(當block發(fā)生copy時,會用到),block的方法。方法實現(xiàn)如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("This is a block.");}

下面我們再看一個結(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為保留字段默認為0
Block_size為sizeof(struct __main_block_impl_0)
,用來表示block所占內(nèi)存大小。因為沒有持有變量,block大小為impl的大小加上Desc指針大小
__main_block_desc_0_DATA
為__main_block_desc_0的一個結(jié)構(gòu)體實例這個結(jié)構(gòu)體,用來描述block的大小等信息。如果持有可修改的捕獲變量時(即加__block
),會增加兩個函數(shù)(copy和dispose),我們后面會分析

再看最重要的一個結(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
里面有兩個變量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)換的代碼和真實運行時有區(qū)別。應(yīng)該為impl.isa = &_NSConcreteGlobalBlock

我們再來看下最開始的
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

我們可以看到,block其實就是指向__main_block_impl_0
的結(jié)構(gòu)體指針,這個結(jié)構(gòu)體包含兩個__block_impl
和__main_block_desc_0
兩個結(jié)構(gòu)體,和一個方法。通過上面的分析,是不是很已經(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實現(xiàn)的分析。是不是很簡單
持有變量的block
我們知道block可以持有變量,現(xiàn)在我們實現(xiàn)一個持有變量的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;}

我們只看下在持有變量時,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)體多了一個變量i。這個變量用來保存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時,取出的i為__main_block_impl_0保存的值,這兩個變量不是同一個。這就是為什么我們執(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)當我們想要修改持有變量時,轉(zhuǎn)化后的代碼有所增加。當我們在變量前面加上__block
時,就會生成一個結(jié)構(gòu)體,來保存變量值。新增了結(jié)構(gòu)體__Block_byref_i_0
,實現(xiàn)如下
struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i;};

__isa指向變量Class
____forwarding,指向自己的指針,當從棧copy到堆時,指向堆上的block
__flags,當block被copy時,標識被捕獲的對象,該執(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進行++運算
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));}

這樣便達到對i值的修改
Block_copy(...)的實現(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)實現(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; }}

當原始block在堆上時,引用計數(shù)+1。當為全局block時,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}

當block在棧上時,調(diào)用Block_copy,block將被copy到堆上。如果block實現(xiàn)了copy和dispose方法,則調(diào)用對應(yīng)的方法,來處理捕獲的變量。
小節(jié)
通過上面的分析,相信大家對block有了更加清晰的理解。??如果大家有興趣,可以看下block在runtime的源碼,結(jié)合我們上面轉(zhuǎn)換的c++代碼,可以看到更完整實現(xiàn)細節(jié)。下節(jié),我將從使用block所引發(fā)的retain cycle問題,來分析runtime的源碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容