淺談Block原理

摘要
block是2010年WWDC蘋(píng)果為Objective-C提供的一個(gè)新特性,它為我們開(kāi)發(fā)提供了便利,比如GCD就大量使用了block,用來(lái)往執(zhí)行隊(duì)列中添加執(zhí)行。那么block到底是什么東西呢。其實(shí)它就是一個(gè)閉包,一個(gè)引用自動(dòng)變量的函數(shù)。很多語(yǔ)言也實(shí)現(xiàn)自己的閉包,比如C#的lamda表達(dá)式。這篇文章將從分析源碼的角度來(lái)分析下block到底是什么鬼。
最簡(jiǎn)單的block,不持有變量
我們先新建一個(gè)源文件:block.c 代碼如下

include <stdio.h>int main(){ void (^blk)(void) = ^(){printf("This is a block.");}; blk(); return 0;}

我們使用clang(LLVM編譯器,和GCC類似),通過(guò)命令clang -rewrite-objc block.c
,解析block.c這樣我們就會(huì)得到對(duì)應(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;}

下面我們來(lái)分析下源碼,看看我們定義的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);

這樣就清晰了。我們寫(xiě)的block被轉(zhuǎn)化成了指向__main_block_impl_0
結(jié)構(gòu)體的指針。構(gòu)造函數(shù)的參數(shù)我們先不管,慢慢一步步分析首先,我們來(lái)看下第一個(gè)struct
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};

isa指針,如果我們對(duì)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.");}

下面我們?cè)倏匆粋€(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)
,用來(lái)表示block所占內(nèi)存大小。因?yàn)闆](méi)有持有變量,block大小為impl的大小加上Desc指針大小
__main_block_desc_0_DATA
為_(kāi)_main_block_desc_0的一個(gè)結(jié)構(gòu)體實(shí)例這個(gè)結(jié)構(gòu)體,用來(lái)描述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ù)用來(lái)初始化變量__main_block_impl_0
和__main_block_desc_0
注:clang轉(zhuǎn)換的代碼和真實(shí)運(yùn)行時(shí)有區(qū)別。應(yīng)該為impl.isa = &_NSConcreteGlobalBlock

我們?cè)賮?lái)看下最開(kāi)始的
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è)方法。通過(guò)上面的分析,是不是很已經(jīng)清晰了最后,main函數(shù)里面的
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

同樣,去除轉(zhuǎn)化代碼,上面的代碼就可以轉(zhuǎn)化為
blk->FuncPtr(blk);

執(zhí)行block函數(shù)
這樣我們就完成了,對(duì)簡(jiǎn)單block實(shí)現(xiàn)的分析。是不是很簡(jiǎn)單
持有變量的block
我們知道block可以持有變量,現(xiàn)在我們實(shí)現(xiàn)一個(gè)持有變量的block。修改下原來(lái)的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è)變量用來(lá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時(shí),取出的i為_(kāi)_main_block_impl_0保存的值,這兩個(gè)變量不是同一個(gè)。這就是為什么我們執(zhí)行了i++操作,再執(zhí)行block,i的值仍然不變的原因
可修改持有變量的block
為了修改持有變量,我們?cè)谧兞壳懊婕由蟔_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)我們?cè)谧兞壳懊婕由蟔_block
時(shí),就會(huì)生成一個(gè)結(jié)構(gòu)體,來(lái)保存變量值。新增了結(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í)被捕獲的對(duì)象,該執(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)化成對(duì)__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á)到對(duì)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中,對(duì)應(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)用對(duì)應(yīng)的方法,來(lái)處理捕獲的變量。
小節(jié)
通過(guò)上面的分析,相信大家對(duì)block有了更加清晰的理解。??如果大家有興趣,可以看下block在runtime的源碼,結(jié)合我們上面轉(zhuǎn)換的c++代碼,可以看到更完整實(shí)現(xiàn)細(xì)節(jié)。下節(jié),我將從使用block所引發(fā)的retain cycle問(wèn)題,來(lái)分析runtime的源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,365評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,561評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,889評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,118評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,619評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評(píng)論 2 370

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