我們先來轉換一個簡單的block代碼: 源:
int main() { void (^blk)(void) = ^{printf();}; blk(); return 0; }
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};struct __main_block_ipml_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();}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() { void (*blk)(void) = (void (*)(void))&__main_block_impl_0( (void *)__main_block_func_0, &__main_block_desc_0_DATA); ((void (*)(struct __block_impl *))( (struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); return 0;}
可以通過轉換後的原始碼看到,通過blocks使用的匿名函數,實際上被作為簡單的c語言函數來處理。 在轉換後的代碼的命名上,是根據block文法所屬的函數名和該block文法在該函數出現的順序值。 來看block的文法: ^{printf();}; 對應的是: static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf(); } 這裡的__cself相當於
c++中的this。
函數參數聲明:struct __main_block_impl_0 *__cself 與c++的this和oc的self相同,參數__cself 是 __main_block_impl_0結構體的指標。
struct __main_block_ipml_0 { struct __block_impl impl; struct __main_block_desc_0 *Desc;}
這裡是取出建構函式的代碼, 其中impl是__block_impl結構體。
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};從名稱中可以看到這些標誌,為今後升級所需的地區還有函數指標。 Desc是指標,是__main_block_desc_0結構體的。
static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size;};這裡也是為今後升級所需區域和block的大小。
有了這些結構體,就應該有初始化:
__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; }可以看到這裡有一個_NSConcreteStackBlock函數,他是使用者初始化__block_impl結構體的isa成員的。
main中建構函式是如何調用的: void (*blk)(void) = (void (*)(void))&__main_block_impl_0( (void *)__main_block_func_0, &__main_block_desc_0_DATA);
拆分:
struct __main_block_impl_0 tmp = __main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp; 這樣就容易理解了。 該原始碼在棧上產生__main_block_impl_0結構體執行個體的指標。我們將棧上產生的__main_block_impl_0結構體執行個體的指標賦值給__main_block_impl_0結構體指標類型的變數blk;
注意到產生tmp的函數的參數, 第一個參數是由block文法轉換的c語言函數指標,第二個參數是作為靜態全域變數初始化的__main_block_desc_0結構體執行個體指標。 看一下結構體初始化: __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; 這裡使用__main_block_impl_0結構體執行個體的大小進行初始化。
來看一下棧上的__main_block_impl_0結構體執行個體(即Block)是圖和根據這些參數進行初始化的:
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};該結構體根據建構函式:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
接下來看blk();
((void (*)(struct __block_impl *))( (struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);我們去掉轉換部分:(*blk->impl.FuncPtr)(blk);
這是簡單的使用函數指標調用函數。 由Block文法轉換的__main_block_func_0函數的指標被賦值成員變數FuncPtr中, __main_block_func_0函數的參數__cself指向Block值, 在調用該函數的原始碼中可以看出Block正是作為參數進行了傳遞。
這裡的isa = &_NSConcreteStackBlock; 將Block指標賦給Block的結構體成員變數isa。 在這裡,我們需要理解oc類和對象的實質, 其實,所謂的block就是oc對象。 id 這個類型是用於儲存oc對象的,在源碼中,雖然可以像使用void*那樣使用id,但是id類型也能夠在c中聲明。 來看id:
struct objc_class { Class isa;};typedef struct objc_class *Class;typedef struct objc_object { Class isa;} *id;id是objc_object結構體的指標類型。
我們來聲明一個oc類:
@interface MyObject :NSObject{ int val0; int val1;}@end
這個類的對象結構體如下:
struct MyObject { Class class; int val0; int val1;};
MyObject類的執行個體變數val0,val1被直接聲明為對象的結構體成員。 在oc中,由類產生對象,意味著 像該結構體這樣,產生由該類產生的對象的結構體執行個體。產生的各個對象,由該類產生的對象的各個結構體執行個體,通過成員變數isa保持該類的結構體執行個體指標。
各類的結構體就是基於objc_class結構體的class_t結構體,class_t結構體:
struct class_t { struct class_t *isa; struct class_t *superclass; Cache cache; IMP *vtable; uintptr_t data_NEVER_USE;};
在oc中,如NSObject的class_t結構體執行個體以及NSMutablearray的class_t結構體執行個體等,均產生並保持各個類的class_t結構體執行個體。 該執行個體特有聲明的成員變數、方法的名稱、方法的實現(即函數指標)、屬性以及父類的指標,並被oc執行階段程式庫所使用。
看剛剛的結構體:
struct __main_block_impl_0 { void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc;};
他就相當於objc_object結構體的oc類對象的結構體。 isa = &NSConcreteStackBlock; 即_NSConcreteStackBlock相當於class_t結構體執行個體, 在將Block作為oc的對象處理時,關於該類的資訊放置在:_NSConcreteStackBlock中。 最後Block即為OC對象。
截獲自動變數值 通過clang進行轉換的原始碼:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 *Desc; const char *fmt; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), 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) { const char &fmt = __cself->fmt; int val = __cself->val; printf(fmt, 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 dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (*blk)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, fmt, val); return 0;}
首先我們看到:Block文法運算式中,使用的自動變數被作為成員變數追加到了__main_block_impl_0中: int val。 如果Block文法運算式沒有使用的自動變數是不會追加的。 Blocks的自動變數截獲只針對Block中使用的自動變數。 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) 這一段時初始設定變數。 在初始化結構體執行個體的時候,根據傳遞給建構函式的參數對由自動變數追加的成員變數進行初始化。 通過:void (*blk)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, fmt, val); 這裡使用執行Block文法時的自動變數fmt和val來初始化__main_block_impl_0結構體執行個體。
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main)block_desc_0_DATA;
fmt = "val = %d\n";
val = 10;
這樣,我們知道 在__main_block_impl_0結構體執行個體中,自動變數值被截獲。
現在這裡是使用block的匿名函數的實現:^{printf(fmt, val);};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char &fmt = __cself->fmt; int val = __cself->val; printf(fmt, val);}
這裡可以看到,截獲到__main_block_impl_0結構體執行個體的成員變數上的自動變數。 這些變數在Block文法運算式之前被聲明定義。 因此,原來的原始碼運算式無需改動便可使用截獲的自動變數值執行。
自動變數就是 在執行Block文法時,Block文法運算式所使用的自動變數值被儲存到Block的結構體執行個體中。
最後,Block不能直接使用c語言數群組類型。看一下數組傳遞的原始碼: void func(char a[10]) { ---; } int main() { char a[10] = {1}; func(a); } 這裡 在建構函式中,將參數賦值給成員變數中,這樣在變換了block文法的函數內 可由成員變數賦值給自動變數: void func(char a[10]) { char b[10] = a; ---; } int main() { char a[10] = {1}; func(a); } 這樣是不能通過編譯的。
__block說明符
在剛剛的代碼:^{printf(fmt, val);};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char &fmt = __cself->fmt; int val = __cself->val; printf(fmt, val);}
中,Block中所使用的被截獲自動變數就如:帶有自動變數值的匿名函數。 所說的,僅截獲自動變數的值。 Block中使用自動變數後,在Block的結構體執行個體中重寫該自動變數也不會改變原先截獲的自動變數。 如果在Block中視圖改變自動變數,將引起編譯錯誤。 但是這樣,我們就不能再block中儲存值了。 解決方案: 1. c中有一個變數,允許block改寫值。 靜態變數 靜態全域變數 全域變數
在Block中,訪問靜態全域變數/全域變數沒有什麼改變,可以直接使用。 靜態變數中,轉換後的函數原本就設定在含有block文法的函數外,所以無法從變數範圍訪問。
int global_val = 1;static int static_global_val = 2;int main() { static int static_val = 3; void (^blk)(void) = ^{ global_val *= 1; static_global_val *= 2; static_val *= 3; }; return 0;}轉換後:
int global_val = 1;static int static_global_val = 2;struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *static_val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags = 0):static_val(_static_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 *static_val = __cself->static_val; global_val *= 1; static_global_val *= 2; (*static_val) *= 3;}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() { static int static_val = 3; blk = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, &static_val); return 0;}
全域變數和靜態全域變數並沒有轉換,而靜態變數的指標對他進行了訪問。 將靜態變數static_val的指標傳遞給__main_block_impl_0結構體的建構函式,並儲存。這是超出範圍後使用變數的最簡單的方法。
2、使用__block說明符 __block儲存域類說明符 __block storage-class-specifier c中的儲存域類說明符: typedef extern static auto register 這裡的__block說明符類似於static 、 auto和register說明符, 他們勇於指定變數值設定到那個儲存地區中。
__block int val = 10;void (^blk)(void) = ^{val = 1;};
編譯後:
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; __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; (val->__forwarding->val) = 1;}static void __main_block_copy_0( struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) { __Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);}static void __main_block_dispose_0(struct __main_block_impl_0 *src) { __Block_object_dispose(src->val, 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() { __Block_byref_val_0 val = { 0, &val, 0, sizeof(__Block_byref_val_0), 10 }; blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000); return 0;}代碼很長, 這個__block變數val是怎樣轉換過來的 ?
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
}; 他變成了結構體。 __block變數也同Block一樣變成了__Block_byref_val_0結構體類型的自動變數, 即在棧上產生的__Block_byref_val_0結構體執行個體。 初始化為10, 這個值也出席那種結構體執行個體的初始化中:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
}; 這意味著 該結構體持有相當於原自動變數的成員變數。 上面結構體中的val相當於原來自動變數的成員變數。
來看給__block變數賦值的代碼: ^{val = 1;} 轉換後:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
} 在給靜態變數賦值的時候 使用了指標。 而向__block變數賦值要更加複雜。 Block的__main_block_impl_0結構體執行個體持有指向__block變數的__Block_byref_val_0結構體執行個體的指標。 在__Block_byref_val_0結構體執行個體的成語變數__forwarding持有指向該執行個體自身的指標。通過__forwarding訪問成員變數val。(這裡的val是該執行個體自身持有的變數,它相當於原自動變數)
關於__forwarding會在續集繼續討論。
-----2014/3/18 Beijing