談?wù)凚lock(一)

? 蘋果在Mac OS X10.6 和iOS 4之后引入block語法,之后就大幅改變了OC 的編程方式。Block 是Cocoa和Cocoa框架的匿名函數(shù)的實現(xiàn)。所謂匿名函數(shù),就是一段具有對象性質(zhì)的代碼段,一方面這一段代碼可以當作函數(shù)來執(zhí)行,另一方面,又可以當作對象來進行傳遞,所以可以讓某代碼段變成某個對象的屬性,或是當作方法或者是函數(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當作參數(shù)的方法的時候 ,則是用以下這種方式調(diào)用。這也是絕大多數(shù)用block當作回調(diào)的處理方式:


把一種block定義成typedef:


Block 也可以當成 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 動畫。當我們想改變一個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.當接收到服務(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.當接收到服務(wù)器返回的數(shù)據(jù)時調(diào)用 該方法可能會被調(diào)用多次

-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data;

3.當請求完成之后調(diào)用該方法不論是請求成功還是請求失敗都調(diào)用該方法,如果請求失敗,那么error對象有值,否則那么error對象為空

-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容