1.簡介數據結構
從iOS4開始,蘋果引入了這個C語言的擴充功能「Blocks」,在一些特定的場景下也是一把利刃。我前面一篇博客中初步介紹了Blocks這個東西,主要是語法的介紹(《iOS中Blocks的介紹》)。app
我曾經看見了老外的一個系列的Blocks介紹,頗有深度(A look inside blocks:Episode 1,A look inside blocks:Episode 2, A look inside blocks:Episode 3),裏面深刻到彙編的層次對Blocks的實現進行了分析。不過若是象我這樣對於彙編不熟悉的人確定也是不少的,理解起來十分痛苦,因而就想到從ObjC自己對Blocks進行的處理裏面來入手分析,看看對於Blocks都悄悄作了什麼。ide
2.環境svn
很簡單,就是Xcode啦。使用的編譯器是CLang,主要是利用了-rewrite-objc這個參數,把源文件轉換成中間文件。這樣就揭開了面紗的一角。我使用的clang編譯器版本是:函數
Apple clang version 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn)
Target: x86_64-apple-darwin12.5.0
Thread model: posix
spa
轉成中間文件的命令是:clang -rewrite-objc 源文件.net
3. 例子1指針
#include <stdio.h> int main(int argc, const char * argv[]) { int val = 2; int val1 = 5; void (^blk)(void) = ^{printf("in Block():val=%d\n", val);}; val = 4; blk(); printf("in main(): val=%d, val1=%d\n", val, val1); return 0; }
代碼的運行結果是:code
in Block():val=2blog
in main():val=4, val1=5
這裏咱們能夠看到Block對於自動變量的「快照」功能。因爲轉成中間文件以後發現長了不少,變成了100多行(不一樣的clang版本轉換出來的文件還不一樣,不過實現部分代碼仍是同樣的),下面的代碼是相關部分的節選,主要說明蘋果是如何實現的。
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
#include <stdio.h> int main(int, const char **); struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int val = __cself->val; // bound by copy printf("in Block():val=%d\n", val);} static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(int argc, const char * argv[]) { int val = 2; int val1 = 5; void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val); val = 4; ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); printf("in main(): val=%d, val1=%d\n", val, val1); return 0; }
中間文件確實看起來複雜了很多,不過仍是有脈絡可循。
看main函數的內容,裏面有個函數指針blk,這個就是指向Block的指針,因此難怪Block變量的聲明和函數指針如此相像(就是把*換成^),編譯器轉換後就是同一個東西啊。
咱們看blk這個函數指針,就是__main_block_impl_0這個結構體變量的指針,這個結構體變量此時已經存在,而後用__main_block_func_0等幾個變量賦初值。咱們能夠看到__main_block_impl_0這個struct中有個val這個項,而且在這裏也賦值了,這就是給變量照的「快照」,因爲這個變量在這裏被記錄了,因此不管外面的val變量如何變化,Block運行時使用的值就始終是「快照」的值了。同時咱們也注意到__main_block_impl_0這個struct中沒有val1這個項,因此說明若是Block中不用到的自動變量是不會自動加入到結構體中的。
Block的運行就是運行__main_block_impl_0這個struct中的FuncPtr這個指針,這個在前面初始化的時候已經被賦值成__main_block_func_0了,因此這裏也就是運行這個函數,並把本身的指針傳入。這裏咱們的實現很是簡單,就是一句printf語句。
4.例子2
#include <stdio.h> int main(int argc, const char * argv[]) { int __block val = 2; int val1 = 5; void (^blk)(void) = ^{printf("in Block():val=%d\n", ++val);}; blk(); printf("in main(): val=%d, val1=%d\n", val, val1); return 0; }
運行結果以下:
in Block():val=3
in main(): val=3, val1=5
一樣展開成中間文件來看蘋果的實現。
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
#include <stdio.h> int main(int, const char **); struct __Block_byref_val_0 { void *__isa; __Block_byref_val_0 *__forwarding; int __flags; int __size; int val; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_val_0 *val; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref printf("in Block():val=%d\n", ++(val->__forwarding->val));} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __main_block_desc_0 { unsigned long reserved; unsigned long 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(int argc, const char * argv[]) { __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 2}; int val1 = 5; void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (struct __Block_byref_val_0 *)&val, 570425344); ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); printf("in main(): val=%d, val1=%d\n", (val.__forwarding->val), val1); return 0; }
中間文件又變長了一些,除去咱們已經瞭解的部分,咱們能夠看到自動變量val再也不是直接加入到__main_block_impl_0裏面,而是又變成了一個__Block_byref_val_0的struct結構體的指針。
main函數裏面對於val的賦值已經變成了對這樣一個數據結構的賦值,第一句上就把2賦給了__Block_byref_val_0裏面的val項,而後在blk這個指針初始化的時候,把__Block_byref_val_0的結構體變量指針傳入__main_block_impl_0。此後全部對於自動變量val的操做都變成對val.__forwarding->val的操做。這樣就解決了Block內外變量同時變化的問題(在操做同一塊內存)。
這裏還看見__Block_byref_val_0裏面有個__forwarding項,這個項是指向自身的一根指針。在blk指針初始化的時候咱們把這個指針的值傳入了__main_block_impl_0。
在__main_block_desc_0裏面,多出了兩個函數指針,分別用於copy和dispose,這兩個函數這裏也是自動生成的。
5.總結
綜合前面的例子來看,Block的實現仍是藉助了C語言的函數指針來實現了,對於普通的自動變量,在Block聲明時會快照內容存儲;對於__block變量,則是生成一個數據結構來存儲,而後取代全部訪問這個變量的地方。
事實上,由於Block運行時徹底可能自動變量的生命週期已經結束,因此Block對於內存的管理是很複雜的,會把內容從棧上copy到堆上(這也是copy和dispose函數的做用)。因此Block雖然威力巨大,但使用時也須要遵循必定的規則。