這是一篇比較長的博文,前部分是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對象呢