Blocks概要
什么是Blocks
Blocks
是C語言的擴充功能。可以用一句話來表示Blocks
的擴充功能:帶有自動變量(局部變量)的匿名函數(shù)。
顧明思義,所謂的匿名函數(shù)就是不帶名稱的函數(shù)。C語言的標準不允許存在這樣的函數(shù)。例如:
int func(int count); //聲明了名稱為func的函數(shù)
int result = func(32); //調(diào)用了名稱為func的函數(shù)
int (*funcptr)(int) = &func;
int result = (*funcptr)(32); //即使通過函數(shù)指針也仍然需要知道函數(shù)名稱
其它語言也有“帶自動變量值的匿名函數(shù)”,或稱為閉包(Closure)、lambda計算等。
Blocks模式
Block語法
完整的形式像這個樣子,由四部分組成^
返回值類型
參數(shù)類型
表達式
例如:
^int (int count){return count++;}
當然Block語法可以省略好幾個項目。首先是返回值類型。結構像這樣^
參數(shù)列表
表達式
省略返回值類型時,如果表達式中有return語句就使用該返回值的類型,如果表達式中沒有return語句就使用void類型。表達式中含有多個return語句時,所有的return的返回值類型必須相同。
例如:
^(int count){return count++;} //該Block語法將按照return語句的類型,返回int型返回值
其次,如果不使用參數(shù),參數(shù)列表也可以省略。結構像這樣^
表達式
例如:
^void (void){printf("Block");}
//省略后為如下形式
^{printf("Block");}
Block類型變量
在C語言中,可以將所定義的函數(shù)的地址賦值給函數(shù)指針類型變量中。例如:
int func(int count) {
return count++;
}
int (*funcptr)(int) = &func;
同樣,在Block語法下,可將Block語法賦值給聲明為Block類型的變量中。即源代碼中一旦使用了Block語法就相當于生成了可賦值給Block類型變量的“值”。Block中由Block語法生成的值也被稱為“Block”。(“Block”既指源代碼中的Block語法,也指Block語法生成的值)
聲明Block類型變量示例如下:
int (^blk)(int);
與前面的使用函數(shù)指針的源代碼對比可知,聲明Block類型變量僅僅是將聲明函數(shù)指針類型變量的“*”變?yōu)椤癪”。該Block類型變量與一般C語言變量完全相同,可作為如下途徑使用。
- 自動變量
- 函數(shù)參數(shù)
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
使用Block語法將Block賦值給Block類型變量示例:
int (^blk)(int) = ^(int count){reutrn count++;};
也可以由Block類型變量向Block類型變量賦值:
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;
在函數(shù)參數(shù)中使用Block類型變量可以向函數(shù)傳遞Block:
void func(int (^blk)(int)) {}
在函數(shù)返回值中指定Block類型,可以將Block作為函數(shù)的返回值返回:
int (^func())(int) {
return ^(int count){return count++;};
}
在函數(shù)參數(shù)和返回值中使用Block類型變量時的記述方式極為復雜,我們可以像使用函數(shù)指針類型時那樣,使用typedef解決問題:
typedef int (^blk_t)(int);
void func(blk_t blk) {} //和 void func(int (^blk)(int)){} 相同
blk func() {return ...} // 和 int (^func())(int){return ...} 相同
如上,我們通過typedef
聲明了blk
類型變量。另外,將賦值給Block類型變量中的Block方法想C語言通常的函數(shù)調(diào)用那樣使用,這種方法與使用函數(shù)指針類型變量調(diào)用函數(shù)的方法幾乎一模一樣:
int result = (*funcptr)(10); //C語言中通過函數(shù)指針調(diào)用函數(shù)
int result = blk(10); //變量blk為Block類型的情況下,這樣調(diào)用Block類型變量
在函數(shù)參數(shù)中使用Block類型變量并在函數(shù)中執(zhí)行Block的例子:
- (int) funcWithBlock:(blk_t)blk rate:(int)rate {
return blk(rate);
}
Block類型變量可完全像通常的C語言變量一樣使用,因此也可以使用指向Block類型變量的指針,即Block的指針類型變量:
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count++;};
blk_t *blkptr = &blk;
(*blkptr)(10);
截獲自動變量值
截獲自動變量值得實例:
int main() {
int val = 32;
const char *fmt = "val = %d";
void (^blk)(void) = ^{print(fmt, val);};
val = 19;
fmt = "changed val = %d";
blk();
return 0;
}
該源代碼中,Block語法的表達式使用的是它之前聲明的自動變量fmt和val。Blocks中,Block表達式截獲所使用的自動變量的值,即保存該自動變量的瞬間值。所以,在執(zhí)行Block語法后,即使改寫B(tài)lock中使用的自動變量值也不會影響B(tài)lock執(zhí)行時自動變量的值。所以,上述執(zhí)行結果:
val = 32
這就是自動變量值得截獲。
__block說明符
自動變量值截獲只能保存執(zhí)行Block語法瞬間的值,保存后就不能改寫該值。如果在Block語法中改寫截獲的自動變量的值,就會產(chǎn)生編譯錯誤。
若想在Block語法的表達式中將值賦給在Block語法外聲明的自動變量,需要在改自動變量上附加__block
說明符。
__block int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %d", val);
該源代碼的執(zhí)行結果為:
val = 1
使用附有__block
說明符的自動變量可在Block中賦值,該變量稱為__block變量
。
截獲的自動變量
如果截獲Objective-C對象,調(diào)用變更該對象的方法也會產(chǎn)生編譯錯誤嗎?
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
這樣子是沒有問題的,但是如果向截獲的變量array賦值則會產(chǎn)生編譯錯誤。截獲的變量值為NSMutableArray類的對象,如果用C語言描述,即是截獲NSMutableArray類對象用的結構體實例指針。雖然賦值給截獲的自動變量array的操作會產(chǎn)生編譯錯誤,但使用截獲的值卻不會有任何問題。如下,則會產(chǎn)生編譯錯誤:
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
array = [[NSMutableArray alloc] init];
};
同理,這里加上__block
說明符就不會產(chǎn)生編譯錯誤了。
另外,在使用C語言數(shù)組時必須小心使用其指針:
const char str[] = "hello world";
void (^blk)(void) = ^{
printf("%c", str[2]);
};
上述代碼會產(chǎn)生編譯錯誤,因為在Blocks中,截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲。這時可以使用指針解決問題:
const char *str = "hello world";
void (^blk)(void) = ^{
printf("%c", str[2]);
};
注:參考書籍《Objective-C高級編程》