有一種 Block 叫 Callback,有一種 Callback 叫 CompletionHandler

有一種 Block 叫 Callback,有一種 Callback 叫 CompletionHandler

【引言】iOS10推送部分的API,大量使用了 CompletionHandler 這種命名方式,那么本文我們將對比下這種 Block 的特殊性,以便更好的理解和在自己的項目中實踐 CompletionHandler 樣式的 Blcok。

原文鏈接: 《有一種 Block 叫 Callback,有一種 Callback 叫 CompletionHandler》

正文

我們作為開發(fā)者去集成一個 Lib (也可以叫輪子、SDK、下文統(tǒng)一叫 Lib)時,我們會發(fā)現(xiàn)我們遇到的 Block, 按照功能的角度劃分,其實可以分為這幾種:

  • Lib 通知開發(fā)者,Lib操作已經(jīng)完成。一般命名為 Callback
  • 開發(fā)者通知 Lib,開發(fā)者的操作已經(jīng)完成。一般可以命名為 CompletionHandler。

這兩處的區(qū)別: 前者是 “Block 的執(zhí)行”,后者是 “Block 的填充”。

Callback vs CompletionHandler 命名與功能的差別,Apple 也沒有明確的編碼規(guī)范指出過,只不過如果按照“執(zhí)行與填充”的功能劃分的話,callbackcompletionHandler 的命名可以區(qū)分開來對待。同時也方便調(diào)用者理解 block 的功能。但總體來說,Apple 官方的命名中,“Block 填充“這個功能一般都會命名為 “completionHandler”,“Block 執(zhí)行”這個功能大多命名為了“callback” ,也有少部分命名為了 “completionHandler”。

比如:

NSURLSession 中,下面的函數(shù)將 “callback” 命名為了 “completionHandler”:

- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

我們常常見到 CompletionHandler 被用到了第一種場景,而第一種場景“Block 執(zhí)行”命名為 Callback 則更合適。

不是所有 Block 都適合叫做 CompletionHandler

一般情況下,CompletionHandler 的設(shè)計往往考慮到多線程操作,于是,你就完全可以異步操作,然后在線程結(jié)束時執(zhí)行該 CompletionHandler,下文的例子中會講述下 CompletionHandler 方式在多線程場景下的一些優(yōu)勢。

CompletionHandler + Delegate 組合

在 iOS10 中新增加的 UserNotificaitons 中大量使用了這種 Block,比如:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center 
didReceiveNotificationResponse:(UNNotificationResponse *)response 
        withCompletionHandler:(void (^)(void))completionHandler;

文檔 對 completionHandler 的注釋是這樣的:

The block to execute when you have finished processing the user’s response. You must execute this block from your method and should call it as quickly as possible. The block has no return value or parameters.

同樣在這里也有應用:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;

還有另外一個也非常普遍的例子(Delegate 方式使用URLSession 時候必不可少的 4個代理函數(shù)之一 )

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                didReceiveResponse:(NSURLResponse *)response
                                 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

在代理方法實現(xiàn)代碼里面,若是不執(zhí)行 completionHandler(NSURLSessionResponseAllow) 話,http請求就終止了。

CompletionHandler + Block 組合

函數(shù)中將函數(shù)作為參數(shù)或者返回值,就叫做高階函數(shù)。

按照這種定義,Block 中將 Block 作為參數(shù),這也就是高階函數(shù)。

結(jié)合實際的應用場景來看一個例子:

如果有這樣一個需求:

拿我之前的一個 IM 項目 ChatKit-OC (開源的,下面簡稱 ChatKit)為例,當你的應用想要集成一個 IM 服務時,可能這時候,你的 APP 已經(jīng)上架了,已經(jīng)有自己的注冊、登錄等流程了。用 ChatKit 進行聊天很簡單,只需要給 ChatKit 一個 id 就夠了。聊天是正常了,但是雙方只能看到一個id,這樣體驗很不好。但是如何展示頭像、昵稱呢?于是就設(shè)計了這樣一個接口,-setFetchProfilesBlock:

這是上層(APP)提供用戶信息的 Block,由于 ChatKit 并不關(guān)心業(yè)務邏輯信息,比如用戶昵稱,用戶頭像等。用戶可以通過 ChatKit 單例向 ChatKit 注入一個用戶信息內(nèi)容提供 Block,通過這個用戶信息提供 Block,ChatKit 才能夠正確的進行業(yè)務邏輯數(shù)據(jù)的繪制。

示意圖如下:

具體實現(xiàn)如下:

方法定義如下:

/*!
*  @brief The block to execute with the users' information for the userIds. Always execute this block at some point when fetching profiles completes on main thread. Specify users' information how you want ChatKit to show.
*  @attention If you fetch users fails, you should reture nil, meanwhile, give the error reason.
*/
typedef void(^LCCKFetchProfilesCompletionHandler)(NSArray<id<LCCKUserDelegate>> *users, NSError *error);

/*!
*  @brief When LeanCloudChatKit wants to fetch profiles, this block will be invoked.
*  @param userIds User ids
*  @param completionHandler The block to execute with the users' information for the userIds. Always execute this block at some point during your implementation of this method on main thread. Specify users' information how you want ChatKit to show.
*/
typedef void(^LCCKFetchProfilesBlock)(NSArray<NSString *> *userIds, LCCKFetchProfilesCompletionHandler completionHandler);

@property (nonatomic, copy) LCCKFetchProfilesBlock fetchProfilesBlock;

/*!
*  @brief Add the ablitity to fetch profiles.
*  @attention  You must get peer information by peer id with a synchronous implementation.
*              If implemeted, this block will be invoked automatically by LeanCloudChatKit for fetching peer profile.
*/
- (void)setFetchProfilesBlock:(LCCKFetchProfilesBlock)fetchProfilesBlock;

用法如下所示:

#warning 注意:setFetchProfilesBlock 方法必須實現(xiàn),如果不實現(xiàn),ChatKit將無法顯示用戶頭像、用戶昵稱。以下方法循環(huán)模擬了通過 userIds 同步查詢 users 信息的過程,這里需要替換為 App 的 API 同步查詢
   [[LCChatKit sharedInstance] setFetchProfilesBlock:^(NSArray<NSString *> *userIds,
                            LCCKFetchProfilesCompletionHandler completionHandler) {
        if (userIds.count == 0) {
            NSInteger code = 0;
            NSString *errorReasonText = @"User ids is nil";
            NSDictionary *errorInfo = @{
                                        @"code":@(code),
                                        NSLocalizedDescriptionKey : errorReasonText,
                                        };
            NSError *error = [NSError errorWithDomain:NSStringFromClass([self class])
                                                 code:code
                                             userInfo:errorInfo];
            
            !completionHandler ?: completionHandler(nil, error);
            return;
        }
        
        NSMutableArray *users = [NSMutableArray arrayWithCapacity:userIds.count];
#warning 注意:以下方法循環(huán)模擬了通過 userIds 同步查詢 users 信息的過程,這里需要替換為 App 的 API 同步查詢
        
        [userIds enumerateObjectsUsingBlock:^(NSString *_Nonnull clientId, NSUInteger idx,
                                              BOOL *_Nonnull stop) {
            NSPredicate *predicate = [NSPredicate predicateWithFormat:@"peerId like %@", clientId];
            //這里的LCCKContactProfiles,LCCKProfileKeyPeerId都為事先的宏定義,
            NSArray *searchedUsers = [LCCKContactProfiles filteredArrayUsingPredicate:predicate];
            if (searchedUsers.count > 0) {
                NSDictionary *user = searchedUsers[0];
                NSURL *avatarURL = [NSURL URLWithString:user[LCCKProfileKeyAvatarURL]];
                LCCKUser *user_ = [LCCKUser userWithUserId:user[LCCKProfileKeyPeerId]
                                                      name:user[LCCKProfileKeyName]
                                                 avatarURL:avatarURL
                                                  clientId:clientId];
                [users addObject:user_];
            } else {
                //注意:如果網(wǎng)絡(luò)請求失敗,請至少提供 ClientId!
                LCCKUser *user_ = [LCCKUser userWithClientId:clientId];
                [users addObject:user_];
            }
        }];
        // 模擬網(wǎng)絡(luò)延時,3秒
        //         sleep(3);
        
#warning 重要:completionHandler 這個 Bock 必須執(zhí)行,需要在你**獲取到用戶信息結(jié)束**后,將信息傳給該Block!
        !completionHandler ?: completionHandler([users copy], nil);
    }];

對于以上 Fetch 方法的這種應用場景,其實用方法的返回值也可以實現(xiàn),但是與 CompletionHandler 相比,無法自由切換線程是個弊端。


原文鏈接: 《有一種 Block 叫 Callback,有一種 Callback 叫 CompletionHandler》

Posted by 微博@iOS程序犭袁

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內(nèi)容