這次介紹一下Block是如何截獲自動變量以及__block是什麼原理???
直接上代碼
#include "stdio.h" int main(){ int a = 100; int b = 200; const char *ch = "b = %d\n"; void (^block)(void) = ^{ printf(ch,b); }; b = 300; ch = "value had changed.b = %d\n"; block(); return 0; }
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; const char *ch; int b; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_ch, int _b, int flags=0) : ch(_ch), b(_b) { 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 *ch = __cself->ch; // bound by copy int b = __cself->b; // bound by copy printf(ch,b); } 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 a = 100; int b = 200; const char *ch = "b = %d\n"; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ch, b)); b = 300; ch = "value had changed.b = %d\n"; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; }
1.首先看看Block的內部結構本尊和沒有截獲的區別
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; const char *ch; int b; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_ch, int _b, int flags=0) : ch(_ch), b(_b) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };根據main函數裡面的Block語法,這裡把Block塊表達式裡面的局部變量作為了這個Block結構體的成員變量追加到了__main_block_impl_0的結構體中
值得注意的是
@ 結構體內聲明的成員變量類型與局部變量類型完全相同
@ 語法中沒有使用的局部變量(例如咱們這裡的a變量)不會被追加
@ 細節需要注意,這裡截獲的ch是不可修改的,而且捕捉的b只是截獲了該局部變量的值而已(下面在將如何截獲指針)
2.再來看看該結構體實例化的構造函數以及調用和沒有截獲的區別
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_ch, int _b, int flags=0) : ch(_ch), b(_b) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ch, b));根據傳遞給構造函數的參數對由局部變量追加的成員變量進行初始化。
這裡傳遞的參數僅僅只是ch和b的值而已
理解為把Block語法塊裡面截獲的局部變量對__main_block_impl_0的成員追加並且傳遞賦值
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
ch = "b=%d\n";
b=200;
由此可以看出,__main_block_impl_0的結構體(即Block)對自動變量進行了截獲
3.再來看看最終調用Block的時候和沒有截獲的區別
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char *ch = __cself->ch; // bound by copy int b = __cself->b; // bound by copy printf(ch,b); }可以看出block()這句代碼轉換的調用方式沒有任何變化
還是轉換為一下這句話,而且把本身結構體作為參數進行了傳遞
(*block-impl.FuncPtr)(block)
現在注意看__main_block_func_0這個C語言的函數
@ 和之前相比這裡對傳遞過來的block結構體參數進行了變量獲取__cself->ch 和 __cself->b(這兩個變量已經在Block表達式之前進行了聲明定義),最終打印出來
總結成一句話
所謂的“截獲自動變量”,無非就是在執行Block語法的時候,Block語法所用到的局部變量值被保存到Block的結構體實例當中(即所謂的Block本體)
看的仔細的同學就能有所發現,Block中所捕獲的變量就猶如“帶有局部變量值的匿名函數”所說,僅僅截獲局部變量的值而已,如果在截獲的Block裡面重寫局部變量也不會改變原先所截獲的局部變量
例如
int a = 0;
void (^block)(void) = ^{a = 1};
這樣寫直接就編譯出錯了!!!
可以直接看__block_main_block_impl_0的實現上,並不能改寫其捕獲變量的值,因此直接報錯了
簡單的分割一下,上面介紹了值的捕獲是如何進行的,那麼如何解決在Block中保存修改值???
第一種方法:運用C語言中的變量
@ 靜態變量
@ 靜態全局變量
@ 全局變量
看段簡單的代碼
#include "stdio.h" int global_val = 1; // 全局變量 static int static_global_val = 2;//靜態全局 int main(){ static int static_val = 3; // 靜態局部 void (^block)(void) = ^{ global_val *= 1; static_global_val *= 2; static_val *= 3; }; block(); printf("%d\n",global_val); printf("%d\n",static_global_val); printf("%d\n",static_val);
沒錯,應該能想到了,能改變的情況就是直接讓Block截獲局部變量的指針,看clang的源碼
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; // bound by copy global_val *= 1; static_global_val *= 2; (*static_val) *= 3; } 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(){ static int static_val = 3; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); printf("%d\n",global_val); printf("%d\n",static_global_val); printf("%d\n",static_val); }
@ 在__main_block_impl_0這個結構體中的追加的成員變量變成了int *_static_val指針了
@ 在結構體實例化函數中__main_block_impl_0參數也變為了&static_val地址了
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_val = __cself->static_val; // bound by copy global_val *= 1; static_global_val *= 2; (*static_val) *= 3; }根據Block語法轉換而來的C靜態函數,使用static_val的指針進行訪問,上面參數可以看出,初始化的時候將靜態變量的static_val指針傳遞給__main_block_impl_0結構體進行追加成員變量初始化並保存,這樣做也是對超出作用於使用變量的最簡單的方法
第二種方法:就是大家所熟知的__block修飾符的使用
__block int a = 0;
void (^block)(void) = ^{a = 1};
把之前的這段編譯錯誤的代碼前面加上static修飾符,再讓clang變成源碼看下,本以為只是很簡單的轉變,看來我還是太年輕了,這代碼直接暴增啊,暴增啊,這看來又能開幾篇博客分析了,越看越多,根本停不下來。。。。。。
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) = 1;} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0}; void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); return 0;
分析分析下,一個__block多了那麼多代碼和結構,真的也是醉了
__block int a = 0;分解如下
__Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),0};
這貨竟然加了_block變成了結構體實例,在棧上生成了__block_byref_a_0的結構體實例,a變量初始化為0,這個值也出現在了這個結構體成員變量變量中,意味著該結構體持有相當於原局部變量的成員變量
來來來,看看這貨結構
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; };
void (^block)(void) = ^{a = 1};分解如下
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));還是調用__main_block_impl_0的初始化結構體,由於int a加了__block修飾符,那麼這個初始化函數傳遞的不再是&a的地址,而是換成__Block_byref_a_0這個結構體實例的指針進行傳遞(該結構體其實也是個對象,最後的屬性放著需要截獲的局部變量)
這裡的__main_block_func_0對應的block塊函數轉換為C
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) = 1;}
_Block_byref_a_0這個結構體實例的成員變量__forwarding持有指向該結構體實例自身的指針。通過__forwarding訪問成員變量a
來一張藝術家畫的圖讓大家欣賞下
另外提一點,__block內部的__Block_byhef_val_0這個也是獨立的結構體實例,不屬於__main_blocl_impl_0結構體中
,這樣方便了在多個block中都能訪問__block變量
這內部結構越講越多了。。。。。。下集預告
@ 這裡的Block超出其作用變量還可以存在的理由?
@ __forwarding變量存在的理由?
@ 還有剛源碼中新增的copy和dispose函數,後面再看吧
還得總結下,不然我也不知道我寫了什麼。。。。。。
1. 普通的局部變量被Block截獲只是單純的截獲其值,並追加到__main_block_impl_0結構體中(無法修改重寫)
2.要允許Block修改值有兩個方法,第一個就是用C語言的變量,代表就是靜態局部變量,第二個就是__block修飾符
3.__block修飾的變量,其實這就是變成了個對象結構體__Block_byref_val_0,初始化__main_block_impl_0的時候追加的是__block生成的結構體指針
4.最終在__main_block_func_0函數中調用__block結構體指針的forwarding指針(這一節指向自己,後面會變)的成員變量a來進行重新賦值
5.其實__block這方法生成的源碼,能大致看出來這其實類似OC裡面對象的retain和release一系列操作,下一節在看吧
其實大概就明白__block修飾的局部變量,其實也是個結構體實例(對象),並且結構體指針作為參數進行初始化和調用
OK,差不多就這樣,後面再介紹吧