這是一篇比較長的博文,前部分是block的測試題目,中間是block的語法、特性,block講解block內部實現和block存儲位置,請讀者耐心閱讀。具備block基礎的同學,直接調轉到block的實現
下面列出了五道題,看看能否答對兩三個。主要涉及block棧上、還是堆上、怎麼捕獲變量。答案在博文最後一行
//-----------第一道題:-------------- void exampleA() { char a = 'A'; ^{ printf("%c\n", a);}; } A.始終能夠正常運行 B.只有在使用ARC的情況下才能正常運行 C.不使用ARC才能正常運行 D.永遠無法正常運行 //-----------第二道題:答案同第一題-------------- void exampleB_addBlockToArray(NSMutableArray *array) { char b = 'B'; [array addObject:^{printf("%c\n", b);}]; } void exampleB() { NSMutableArray *array = [NSMutableArray array]; exampleB_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); } //-----------第三道題:答案同第一題-------------- void exampleC_addBlockToArray(NSMutableArray *array) { [array addObject:^{printf("C\n");}]; } void exampleC() { NSMutableArray *array = [NSMutableArray array]; exampleC_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); } //-----------第四道題:答案同第一題-------------- typedef void (^dBlock)(); dBlock exampleD_getBlock() { char d = 'D'; return ^{printf("%c\n", d);}; } void exampleD() { exampleD_getBlock()(); } //-----------第五道題:答案同第一題-------------- typedef void (^eBlock)(); eBlock exampleE_getBlock() { char e = 'E'; void (^block)() = ^{printf("%c\n", e);}; return block; } void exampleE() { eBlock block = exampleE_getBlock(); block(); }
注:以上題目摘自:CocoaChina論壇點擊打開鏈接
int main(){ void (^blk)(void) = ^{printf("block\n");}; blk(); return 0; }經過 clang -rewrite-objc 之後,代碼編程這樣了(簡化後代碼,讀者可以搜索關鍵字在生成文件中查找):
struct __block_impl{ void *isa; int Flags; int Reserved; void *FuncPtr; }; 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)}; struct __main_block_impl_0{ struct __block_impl impl; struct __main_block_desc_0 *Desc; } static struct __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("block\n"); } int main(){ struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA); (*blk->impl.FuncPtr)(blk); }很多結構體,很多下劃線的變量和函數名。我們一個個來: __block_impl:更像一個block的基類,所有block都具備這些字段。
__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; }總結:所謂block就是Objective-C的對象
int val = 10; void (^blk)(void) = ^{printf("val=%d\n",val);}; val = 2; blk();
上面這段代碼,輸出值是:val = 10.而不是2.block截獲自動變量的瞬時值。因為block保存了自動變量的值,所以在執行block語法後,即使改寫block中使用的自動變量的值也不會影響block執行時自動變量的值。
嘗試改寫block中捕獲的自動變量,將會是編譯錯誤。我更喜歡把這個理解為:block捕獲的自動變量都將轉化為const類型。不可修改了 解決辦法是將自動變量添加修飾符 __block;那麼如果截獲的自動變量是OC對象呢__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; int val; }這個val是如何傳遞到block結構體中的呢?
int main(){ struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val); }注意函數調用最後一個參數,即val參數。 那麼函數調用的代碼頁轉化為下面這樣了.這裡的cself跟C++的this和OC的self一樣。
static struct __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("val=%d\n",__cself-val); }所以,block捕獲變量更像是:函數按值傳遞。
__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; }__main_block_impl_0中自然多了__block_byreg_val_0的一個字段。注意:__block_byref_val_0結構體中有自身的指針對象,難道要 _block int val = 10;這一行代碼,轉化成了下面的結構體 __block)byref_val_0 val = {0,&val,0,sizeof(__block_byref_val_0),10};//自己持有自己的指針。 它竟然變成了結構體了。之所以為啥要生成一個結構體,後面在詳細講講。反正不能直接保存val的指針,因為val是棧上的,保存棧變量的指針很危險。
typedef int (^blk_t)(int); for(...){ blk_t blk = ^(int count) {return count;}; }雖然,這個block在循環內,但是blk的地址總是不變的。說明這個block在全局段。 【要點2】一種情況在非ARC下是無法編譯的: typedef int(^blk_t)(int); blk_t func(int rate){ return ^(int count){return rate*count;} } 這是因為:block捕獲了棧上的rate自動變量,此時rate已經變成了一個結構體,而block中擁有這個結構體的指針。即如果返回block的話就是返回局部變量的指針。而這一點恰是編譯器已經斷定了。在ARC下沒有這個問題,是因為ARC使用了autorelease了。 【要點3】有時候我們需要調用block 的copy函數,將block拷貝到堆上。看下面的代碼:
-(id) getBlockArray{ int val =10; return [[NSArray alloc]initWithObjects: ^{NSLog(@"blk0:%d",val);}, ^{NSLog(@"blk1:%d",val);},nil]; } id obj = getBlockArray(); typedef void (^blk_t)(void); blk_t blk = (blk_t){obj objectAtIndex:0}; blk();這段代碼在最後一行blk()會異常,因為數組中的block是棧上的。因為val是棧上的。解決辦法就是調用copy方法。 【要點4】不管block配置在何處,用copy方法復制都不會引起任何問題。在ARC環境下,如果不確定是否要copy block盡管copy即可。ARC會打掃戰場。 注意:在棧上調用copy那麼復制到堆上,在全局block調用copy什麼也不做,在堆上調用block 引用計數增加 【注意】本人用Xcode 5.1.1 iOS sdk 7.1 編譯發現:並非《Objective-C》高級編程這本書中描述的那樣 int val肯定是在棧上的,我保存了val的地址,看看block調用前後是否變化。輸出一致說明是棧上,不一致說明是堆上。
typedef int (^blkt1)(void) ; -(void) stackOrHeap{ __block int val =10; int *valPtr = &val;//使用int的指針,來檢測block到底在棧上,還是堆上 blkt1 s= ^{ NSLog(@"val_block = %d",++val); return val;}; s(); NSLog(@"valPointer = %d",*valPtr); }在ARC下——block捕獲了自動變量,那麼block就被會直接生成到堆上了。 val_block = 11 valPointer = 10 在非ARC下——block捕獲了自動變量,該block還是在棧上的。 val_block = 11 valPointer = 11
調用copy之後的結果呢:
-(void) stackOrHeap{ __block int val =10; int *valPtr = &val;//使用int的指針,來檢測block到底在棧上,還是堆上 blkt1 s= ^{ NSLog(@"val_block = %d",++val); return val;}; blkt1 h = [s copy]; h(); NSLog(@"valPointer = %d",*valPtr); }
----------------在ARC下>>>>>>>>>>>無效果。 val_block = 11 valPointer = 10
----------------在非ARC下>>>>>>>>>確實復制到堆上了。 val_block = 11 valPointer = 10static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){ _Block_objct_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); }BLOCK_FIELD_IS_BYREF代表是變量。BLOCK_FIELD_IS_OBJECT代表是對象 【__block變量和對象】
int val = 10; void (^blk)(void) = ^{printf("val=%d\n",val);}; val = 2; blk();
上面這段代碼,輸出值是:val = 10.而不是2.block截獲自動變量的瞬時值。因為block保存了自動變量的值,所以在執行block語法後,即使改寫block中使用的自動變量的值也不會影響block執行時自動變量的值。
嘗試改寫block中捕獲的自動變量,將會是編譯錯誤。我更喜歡把這個理解為:block捕獲的自動變量都將轉化為const類型。不可修改了 解決辦法是將自動變量添加修飾符 __block;那麼如果截獲的自動變量是OC對象呢