使用clang的rewrite-objc filename 能夠將有block的c代碼轉換成cpp代碼。從中能夠看到block的實現。 函數
#include <stdio.h> int main() { void (^blk)(void) = ^{ printf("Block\n"); }; blk(); return 0; }使用clang rewrite-objc之後會看到block的實現
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; } };
能夠看到其實block是一個正常的OC類 spa
來看看block是怎樣訪問外部變量的 指針
int main() { int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (^blk)(void) = ^{ printf(fmt, val); }; return 0; }
轉換以後,能夠看到 code
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; } };
int main() { int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val); return 0; }
block的變量會被複制進block中 get
若是當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; }; blk(); return 0; }
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; }
因爲全局變量是在Data Section中,因此直接能夠訪問。局部靜態變量是經過將其指針傳入到block中,block就能夠對其值進行修改。 it
而後看一下__block修飾符變量 io
int main() { __block int val = 10; void (^blk)(void) = ^{val = 1;}; blk(); return 0; }
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 (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((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
__block修飾符的變量,會生成一個__Block_byref_val_0的struct,而後經過訪問其__forwarding來訪問val值。由於Block有多是在stack或者heap中,因此用__forwarding來訪問。之因此會將__block單獨生成一個struct是由於可能該變量會被多個block使用。 編譯
Block分三種類型 class
0) NSConcreteStackBlock --stack
1) NSConcreteGlobalBlock --data area
2) NSConcreteMallocBlock --heap
自動copy block
當開啓ARC時,在某些狀況編譯器會自動copy block,從stack到heap。
typedef int (^blk_t)(int); blk_t func(int rate) { return ^(int count){return rate * count;}; }
blk_t func(int rate) { blk_t tmp = &__func_block_impl_0( __func_block_func_0, &__func_block_desc_0_DATA, rate); tmp = objc_retainBlock(tmp); return objc_autoreleaseReturnValue(tmp); }
有些狀況,編譯器是沒法檢測是否應該copy block:
當block做爲參數傳遞到方法或函數中。
可是,若是該方法或函數在內部copy,就不用手動再copy:
0)cocoa framework方法, 有usingBlock
1) GCD API
- (id) getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk0:%d", val);} copy], [^{NSLog(@"blk1:%d", val);} copy], nil]; }
__forwarding
當block從stack copy到 heap中時,block中用到的__block也會copy到heap中,而且copy到heap中的block擁有該__block。
__block int val = 0; void (^blk)(void) = [^{++val;} copy]; ++val; blk(); NSLog(@"%d", val);
當block copy到heap中後, stack中的__forwarding會指向heap中的__block, heap中的__forwarding會指向本身的__block值,這樣保證了__forwarding指向的是同一個變量值。
Block何時會copy到heap中
0)對block調用copy方法。
1)block做爲一個函數的返回值。 編譯器自動copy
2)賦值給id或block type class 有__strong 修飾符的成員變量。 編譯器自動copy
3)usingBlock, GCD API。 在函數內copy
何時用該copy block?
0) block是函數返回值
1) block賦值給id或block type class 有__strong 修飾符的成員變量。
2)3)usingBlock, GCD API。 在函數內copy