? 蘋果在Mac OS X10.6 和iOS 4之后引入block語法终娃,之后就大幅改變了OC 的編程方式。Block 是Cocoa和Cocoa框架的匿名函數(shù)的實現(xiàn)券敌。所謂匿名函數(shù),就是一段具有對象性質(zhì)的代碼段,一方面這一段代碼可以當(dāng)作函數(shù)來執(zhí)行扁达,另一方面,又可以當(dāng)作對象來進行傳遞蠢熄,所以可以讓某代碼段變成某個對象的屬性跪解,或是當(dāng)作方法或者是函數(shù)的參數(shù)傳遞,也是因為這種特性签孔,我們使用block 來實現(xiàn)回調(diào)叉讥。
在Block之前,最常見的是使用代理來處理回調(diào)(或者使用較具c語言風(fēng)格的方式饥追,傳遞回調(diào)函數(shù)的指針图仓,或者使用target/action pattern)。在iOS 4有了block之后但绕,蘋果改寫了UIKit的api,把原來使用代理的地方換成了使用block救崔。
Block 語法
一直以來還是有不少人不滿block的語法惶看,甚至有人搞了個叫做http://fuckingblocksyntax.com?的網(wǎng)站,“fucking” 呵呵六孵。
將block定義成變量:
定義成property 的語法:
定義成方法的參數(shù)的語法是:
在執(zhí)行某個需要傳入block當(dāng)作參數(shù)的方法的時候 纬黎,則是用以下這種方式調(diào)用。這也是絕大多數(shù)用block當(dāng)作回調(diào)的處理方式:
把一種block定義成typedef:
Block 也可以當(dāng)成 C 函數(shù)的參數(shù)或是返回值的類型劫窒,但是本今,在這種狀況下, 我們不能夠直接使用 returnType (^)(parameterTypes) 這種語法烛亦,必須要先定義成 typedef 才行诈泼。也就是說,這樣是不合法的:
(void (^)(void)) test ( (void (^)(void)) ?block) {
return?block;
}
但可以寫成這樣:
typedef void (^TestBlock)(void);
TestBlock test(TestBlock block) {
return block;
}
雖然 C 語言的函數(shù) 的參數(shù)不能夠使用 returnType (^)(parameterTypes) 語法煤禽,但是一 個 block 倒是可以使用這種語法編寫輸入與返回值的類型铐达,但其實在這種情況下, 還是會比較建議使用 typedef 定義檬果。比方說瓮孙,我們現(xiàn)在要定義一個 block,這個 block 會返回另外一個類型為 int(^)(void) 的 block选脊,就會寫成這樣:
int (^(^counter_maker)(void))(void) = ^ {
? ? ? ? ? __block int x = 0;
? ? ? ? ? return ^ {
? ? ? ? ? ? ?return ++x;
};
};
但是這樣做 杭抠,可讀性極差,我們再來試試下面這這種吧:
typedef int (^CounterMakerBlock)(void);?
CounterMakerBlock (^counter_maker)(void) = ^ {
? ? ? ? ? ?__block int x = 0;
? ? ? ? ? ?return ^ {
? ? ? ? ? ?return ++x;
?};
?};
Block 如何代替了 Delegate
要想知道block的使用場景恳啥,不妨先看看蘋果官方是怎樣使用的偏灿。
首先是UIView 動畫。當(dāng)我們想改變一個ui 控件的frame,并加上一些動畫钝的,讓其顯得不那么生硬翁垂,我們常常會使用到UIView Animation.
栗如,我們想改變某個subView的frame:
self.subview.frame = CGRectMake(10, 10, 100, 100);
在ios4的時代硝桩,我們需要使用UIView的+beginAnimations:context: 與 +commitAnimations 兩個類方法沿猜,把原本的代碼 包起來,那么碗脊,在這兩個類方法之間的代碼就會產(chǎn)生動畫效果啼肩。
[UIView beginAnimations:@"animation" context:nil]; ? ? ? ? ? ? ?
?self.subview.frame = CGRectMake(10, 10, 100, 100); ? ? ? ? ? ? ? ? ? ? ? ? ?
?[UIView commitAnimations];
倘若我們想要在這段動畫結(jié)束的時候去做一件事情,比如執(zhí)行另一個動畫衙伶,我們應(yīng)如何呢祈坠?ios 4 之前是遵守UIView代理,實現(xiàn)其中的animationDidStop代理方法矢劲。
- (void)moveView ?{
?[UIView beginAnimations:@"animation" context:nil]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?[UIView setAnimationDelegate:self]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? self.subview.frame = CGRectMake(10, 10, 100, 100); ? ? ? ? ? ?[UIView commitAnimations]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
} ? ? ? ? ? ? ? ? ? ? ? ? ? ?
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // do something
}
由此可見? 颁虐,使用代理設(shè)計模式,最終的結(jié)果會導(dǎo)致代碼分散卧须。但是block之后另绩,我們可以將“動畫該做什么”和”動畫完成之后該做什么寫道一起了:
- (void)moveView { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?[UIView animateWithDuration:0.25 animations:^{ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? self.subview.frame = CGRectMake(10, 10, 100, 100); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?} completion:^(BOOL finished) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// Do something ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
另外像NSArray的排序儒陨,以往我們必須使用c函數(shù)指針或者是selector的方式:
NSArray*arr = [[NSArrayalloc] initWithObjects:@1, @2, @3,nil];
SELsel =@selector(compare:);
arr = [arr sortedArrayUsingSelector:sel];
也可用block:
?NSArray*arr = [[NSArrayalloc] initWithObjects:@1, @2, @3,nil]; ?
?NSArray *arr= [array sortedArrayUsingComparator: ^NSComparisonResult(id obj1, id
?obj2) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?return [obj1 compare:obj2]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
}];
什么時候用 Blocks,什么時候用 Delegate笋籽?
即使block可以取代代理處理回調(diào)蹦漠,但是蘋果自己的api設(shè)計中可以看到,并不是所有的代理都被block的取代车海,在cocoa與cocoatouch中笛园,仍然大量使用代理。那么我們就要問了:究竟什么時候我們該用代理侍芝,什么時候用block呢
區(qū)分它們的方法是:如果我們調(diào)用一個方法研铆,這個方法只有一個單一的回調(diào),那么就使用block州叠,如果可能有多個不同的回調(diào)棵红,那就使用代理。
好處是:如果我們調(diào)用一個方法咧栗,這個方法只有一個單一的回調(diào)逆甜,很有可能是有一些回調(diào)是非必須實現(xiàn)的。如果是使用代理致板,那么交煞,在代理需要實現(xiàn)的protocol中,我們可以用@required與@optional區(qū)分哪些是需要實現(xiàn)的代理方法斟或;但如果用block就很難做出這樣的區(qū)分了素征,尤其是在xcode6.3之前,oc還沒有nullable,nonnull等關(guān)鍵字萝挤,去讓我們知道某個property,或者某個方法的傳入?yún)?shù)block可否為nil御毅,我們也搞不清傳入nil會發(fā)生什么可怕的事情。
舉個栗子平斩。在ios 7之后亚享,蘋果鼓勵開發(fā)者使用NSURLSession 處理網(wǎng)絡(luò)連接咽块,NSURLSession就體現(xiàn)了單一回調(diào)使用block绘面,多重回調(diào)使用代理這一點。
如果我們要從后臺獲取數(shù)據(jù)侈沪,我們只需創(chuàng)建一個NSURLSessionDataTask類的對象揭璃,一般來說,我們只需要處理「這個鏈接做完事情的 下一步該做什么」亭罪,所以一般我們只要實現(xiàn)這個task 的 completion handler瘦馍,就是鏈接完成后要執(zhí)行的block;一般鏈接結(jié)束后就是成功獲取數(shù)據(jù)或者鏈接失敗兩種情況:
NSURL *URL = [NSURL URLWithString:@"http://baidu.com"];
?NSURLRequest ?*request = [NSURLRequest requestWithURL:URL]; ? ? ? ? ? ? ? ? ? ? ? ? ??
NSURLSessionDataTask*task = [[[NSURLSession ?sharedSession]dataTaskWithRequest:request completionHandler:^(NSData* _Nullable data, NSURLResponse * _Nullable response,NSError* _Nullable error)
{
// code after completion of task
}];
[task resume];
但,NSURLSession 本身還有 delegate应役。情组。我們在發(fā)送鏈接的時候燥筷,除了處理聯(lián)線結(jié)束要做什麼之外,有時候也可能會處理中途發(fā)生的各種狀況院崇,像 是:HTTP 收到 302 轉(zhuǎn)址肆氓、遇到有問題的 SSL驗證、server 要求用?輸入賬戶密碼底瓣,這些狀況我們要不要提示使用者谢揪?或,如果這是一個傳遞大文檔捐凭、很花時間 的鏈接拨扶,我們有沒有必要上傳進度?這些狀況還是會傳遞給給NSURLSession 的 delegate茁肠,而如果我們要處理這些狀況患民,就要實現(xiàn)以下這些 代理方法。
1.當(dāng)接收到服務(wù)器響應(yīng)的時候調(diào)用
session:發(fā)送請求的session對象
dataTask:根據(jù)NSURLSession創(chuàng)建的task任務(wù)
response:服務(wù)器響應(yīng)信息(響應(yīng)頭)
completionHandler:通過該block回調(diào)官套,告訴服務(wù)器端是否接收返回的數(shù)據(jù)
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnullvoid(^)(NSURLSessionResponseDisposition))completionHandler;
2.當(dāng)接收到服務(wù)器返回的數(shù)據(jù)時調(diào)用 該方法可能會被調(diào)用多次
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data;
3.當(dāng)請求完成之后調(diào)用該方法不論是請求成功還是請求失敗都調(diào)用該方法酒奶,如果請求失敗,那么error對象有值奶赔,否則那么error對象為空
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error