淺談Block原理

摘要
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的源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末城舞,一起剝皮案震驚了整個(gè)濱河市轩触,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌家夺,老刑警劉巖脱柱,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拉馋,居然都是意外死亡榨为,警方通過查閱死者的電腦和手機(jī)惨好,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來随闺,“玉大人日川,你說我怎么就攤上這事【乩郑” “怎么了龄句?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長散罕。 經(jīng)常有香客問我分歇,道長,這世上最難降的妖魔是什么欧漱? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任职抡,我火速辦了婚禮,結(jié)果婚禮上误甚,老公的妹妹穿的比我還像新娘缚甩。我一直安慰自己,他們只是感情好窑邦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布擅威。 她就那樣靜靜地躺著,像睡著了一般奕翔。 火紅的嫁衣襯著肌膚如雪裕寨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天派继,我揣著相機(jī)與錄音宾袜,去河邊找鬼。 笑死驾窟,一個(gè)胖子當(dāng)著我的面吹牛庆猫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绅络,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼月培,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恩急?” 一聲冷哼從身側(cè)響起杉畜,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衷恭,沒想到半個(gè)月后此叠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡随珠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年灭袁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了猬错。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茸歧,死狀恐怖倦炒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情软瞎,我是刑警寧澤逢唤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站铜涉,受9級特大地震影響智玻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芙代,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盖彭。 院中可真熱鬧纹烹,春花似錦、人聲如沸召边。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隧熙。三九已至片挂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贞盯,已是汗流浹背音念。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躏敢,地道東北人闷愤。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像件余,于是被迫代替她去往敵國和親讥脐。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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

  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動(dòng)變量值 int main(){ ...
    南京小伙閱讀 911評論 1 3
  • 前言 Blocks是C語言的擴(kuò)充功能啼器,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,757評論 0 23
  • Block基礎(chǔ)回顧 1.什么是Block旬渠? 帶有局部變量的匿名函數(shù)(名字不重要,知道怎么用就行)端壳,差不多就與C語言...
    Bugfix閱讀 6,748評論 5 61
  • 序言:翻閱資料告丢,學(xué)習(xí),探究更哄,總結(jié)芋齿,借鑒腥寇,謝謝探路者,我只是個(gè)搬運(yùn)工觅捆。參考赦役、轉(zhuǎn)發(fā)資料:http://www.cnbl...
    Init_ZSJ閱讀 899評論 0 1
  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C語言的擴(kuò)充功能——“帶有自動(dòng)變量(即局部...
    SkyMing一C閱讀 2,320評論 6 18