Blocks能夠從封閉的空間中拿到值
除了包含可執(zhí)行的代碼誉帅,一個block同樣有能力區(qū)捕捉到封閉空間內(nèi)的狀態(tài)。打個比方說如果你將block的申明顯示在一個方法里面氧卧,在這個封閉的方法內(nèi)部獲得任何值可能就成了一件很容易的事情桃笙,像下面這樣:
-(void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog("@Integer is:%i",anInteger);
};
testBlock();
}
在這個例子中,anInteger是一個申明在block外部的變量沙绝,但是這個變量的值卻可以在block被定義了之后在其內(nèi)部被捕捉到搏明。這個變量的值只能是被捕捉到的那個值,即便你有其他具體的說明或修改闪檬,它還是當(dāng)時你捉到的那個變量值星著!這個意味著如果你想改變這個外部變量的值在你定義block和實現(xiàn)這個block這段時間之內(nèi),像下面這樣:
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is:%i",anInteger);
};
anInteger = 84;
testBlock();
通過上述例子我們可以發(fā)現(xiàn)谬以,這個變量的值在block中并沒有被影響到强饮,這個意味著日志的輸出始終會顯示:
Integer is :42
這個同樣也說明了block是無法更改原始變量的值,或者甚至是被捕捉到的值(這個被捕捉到的變量值對于block來說就是一個const的變量)
note:你可以嘗試在block內(nèi)部去修改變量值为黎,你發(fā)現(xiàn)會報錯邮丰;
另外,在block定義之前你去改變變量的值是可以的铭乾,block定義之后被捕捉到的話剪廉,這個值的外部變化對它是沒有任何影響的。
利用__block變量來共享內(nèi)存
如果你有這個需要在block內(nèi)部將捕捉到的變量值進(jìn)行修改的需求炕檩,你可以通過使用存儲類型修飾符__block來修飾原始變量斗蒋。這個意味著變量生活的內(nèi)存被分享在原始變量的詞匯范圍和任何有block聲明的范圍之間。
舉個例子來說笛质,你可能會像這樣重寫前面提到的例子:
__block int anInteger = 42;
void (^testBlock) (void) = ^{
NSLog(@"Integer is :%i",anInteger);
};
anInteger = 84;
testBlock();
因為anInteger被申明成一個__block變量泉沾,所以它的存儲和block申明共享。這也意味著日志的輸出將會變成下面這個樣子:
Integer is :84
它同樣也意味著block可以改變原始變量的值妇押,像下面這樣:
__block int anInteger = 42;
void(^testBlock)(void) = ^{
NSLog("@Integer is :%i",anInteger);
anInteger = 100;
};
testBlock();
NSLog(@"Value of original variable is now:%i",anInteger);
這一次跷究,日志的輸出將會顯示:
Integer is :42
Value of original variable is now:100
你可以將Blocks作為方法或函數(shù)的參數(shù)傳遞
本章節(jié)前面講的每一個例子都是blocks直接在被定義了之后進(jìn)行調(diào)用。在實際中敲霍,blocks作為方法或函數(shù)調(diào)用中的參數(shù)傳遞也是隨處可見俊马。例如丁存,你可能會通過線程:Grand Central Dispatch 在后臺調(diào)用block,或者去定義一個block去代表重復(fù)調(diào)用的任務(wù)柴我,就比如說當(dāng)枚舉一個集合的時候解寝。
blocks同樣被用來作為回調(diào),定義任務(wù)完成時要執(zhí)行的代碼艘儒。例如聋伦,你的app可能現(xiàn)在需要通過創(chuàng)建一個復(fù)雜的任務(wù)對象去響應(yīng)一個用戶類似請求web網(wǎng)絡(luò)服務(wù)的動作。
這個很有可能通過使用代理來完成:你可能需要去創(chuàng)建一個合適的代理協(xié)議彤悔,實現(xiàn)被要求使用到的方法嘉抓,設(shè)置這個任務(wù)的代理對象,然后等待一旦這個任務(wù)結(jié)束你創(chuàng)建的代理對象去調(diào)用代理方法晕窑。
然而,Blocks可以讓這些變得更簡單卵佛,因為你可以定義一個回調(diào)行為在你初始化這個任務(wù)的時候杨赤,像下面這樣:
-(IBAction)fetchRemoteInformation:(id)sender {
[self showProgressIndicator];
XYZWebTask *task = ....
[task beginTaskWithCallbackBlock:^{
[self hideProgressIndicator];
}];
}
這個例子中,調(diào)用了一個方法去顯示進(jìn)度指示器截汪,然后創(chuàng)建并開啟一個任務(wù)疾牲。告訴回調(diào)的block在指定任務(wù)完成之后要實現(xiàn)的代碼。在這個例子中衙解,調(diào)用一個方法去隱藏進(jìn)度指示器看起來是很簡單的阳柔。但是請注意,block在回調(diào)的時候蚓峦,為了調(diào)用hideProgressIndicator方法需要去捕捉self對象舌剂。很重要的一點事在捕捉self對象的時候要格外小心。因為我們很容易就會創(chuàng)建一個循環(huán)的強(qiáng)引用暑椰。這個在之后的介紹中我們會提到霍转。
在代碼可讀性方面,這個blocks使得我們可以在一個地方就能很容易的看到任務(wù)發(fā)生的開始和結(jié)束一汽,避免了我們需要去追蹤代理方法來尋找出任務(wù)是怎么發(fā)生的避消。
聲明beginTaskWithCallbackBlock:方法可以像下面這個例子一樣:
-(void)beginTaskWithCallbackBack:(void(^)(void))callbackBlock;
(void(^)void))說明這個參數(shù)是一個沒有任何變量或返回值的block,實現(xiàn)方法可以通過一個很常規(guī)的途徑來調(diào)用這個block召夹,像下面這樣:
-(void)beginTaskWithCallbackBlock:(void(^)(void))callbackBlock {
...
callbackBlock();
}
使用一個或多個參數(shù)的block方法函數(shù)也是同樣的方式:
-(void)doSomethingWithBlock:(void(^)(double,double))block {
...
block(21.0,2.1);
}