[iOS] [OC] 關於block回調、高階函數「回調再調用」及項目實踐

1/3 回調

使用block進行回調處理是十分便利的處理方式,在UIKit的設計中也家常便飯,例如:api

  • UIView動畫,動畫執行後調用completion內的block代碼。
+ (void)animateWithDuration:(NSTimeInterval)duration
                 animations:(void (^)(void))animations
                 completion:(void (^ __nullable)(BOOL finished))completion;
複製代碼
  • 模態展現一個頁面,在展現結束後調用completion內的block代碼。
- (void)presentViewController:(UIViewController *)viewController
                     animated:(BOOL)flag
                   completion:(void (^)(void))completion;
複製代碼
  • 用於實現紙質打印的控制器UIPrintInteractionController,其模態展現方式,一樣是展現結束後調用completionblock代碼。
- (BOOL)presentAnimated:(BOOL)animated 
      completionHandler:(nullable UIPrintInteractionCompletionHandler)completion;
複製代碼

此外,在 WKWebView 中分析JavaScript代碼時也有相似應用,實際開發中存在 completioncompletionHandler 或者callBack等不一樣的命名方式,歸根結底目的都是實現一個事件完成後的「回調做用」,與使用「委託模式」的 delegate + protocol 有殊途同歸之妙,這也是不少二級頁面控制器或者視圖的回調流行使用一個 block 屬性來作回調處理的緣由。bash

而相似 UIView 動畫的 animationsblock動畫參數,以及自動佈局框架Masonry 的設置約束的 make/update/remake 中 block 使用,以及實例初始化方法中的block的應用,則用於更便利地囊括接口設計者的意圖,好比開源網絡框架XMNetworking的請求構造方法或者七牛雲上傳的管理類QNUploadManager的配置構造方法等,便可在 block 內便利地對請求參數進行配置,對外提供API時省去了相似 [[XMRequest alloc] init]實例初始化這一步。網絡

[XMCenter sendRequest:^(XMRequest * _Nonnull request) {
    request.api = @"example/blabla";
    request.httpMethod = kXMHTTPMethodGET;
} ];
複製代碼

2/3 回調再調用

上文說起委託模式也是典型的回調方式之一,在iOS 應用程序的入口就採用了委託模式,即整個應用程序UIApplication單例的及其委託對象AppDelegateUIApplicationDelegate協議中聲明瞭諸多可選optional方法,將程序的運行狀況相關事件/狀態回調給委託者AppDelegate。與本文關聯的是,自iOS 7後系統升級了遠程推送策略而新增了一系列 API,其中就包括UIApplicationDelegate 協議中一個使用block 的協議方法以下(含典型實現):app

- (void)application:(UIApplication *)application
        didReceiveRemoteNotification:(NSDictionary *)userInfo
        fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 當收到推送後異步加載一些數據
        // 而後告知回去數據的結果狀況
        completionHandler(UIBackgroundFetchResultNewData);
    });
}
複製代碼

此協議方法是告知AppDelegate程序收到了遠程推送,AppDelegate 能夠作一些獲取數據的處理,並要求在獲取數據完成後調用completionHandler告知 UIApplication 獲取數據的結果狀況,從而讓 UIApplication 來估算電量和數據消耗狀況,做爲系統進行資源管理的一部分,要求completionHander必須儘快調用(30 s之內),這個場景就是回調再調用,其中:框架

  1. 這是UIApplication委託的協議方法,而不是其實例方法,是 UIApplication調用 AppDelegate 的方法,即回調
  2. 在回調的協議方法中,攜帶一個block類型的參數,將一段代碼傳遞給 AppDelegate ,並要求 AppDelegate完成業務邏輯後執行此block代碼,以達到調用UIApplication的目的,即回調再調用

這類將block做爲參數或者返回值使用一般稱爲高階函數異步

這種設計方式,有一種變換的實現方式:由UIApplication單獨再提供一個 APIAppDelgate來主動調用,寫一個僞代碼方法以下:async

// UIApplication 類的僞代碼
// 處理 delegate 後臺獲取數據後的結果
- (void)handleBackgroundFetchResult:(UIBackgroundFetchResult)result;
複製代碼

則,上述協議方法及其典型實現,能夠替換爲以下僞代碼:函數

// 注意移除了 回調的 block,改成直接調用僞代碼 API
- (void)application:(UIApplication *)application
        didReceiveRemoteNotification:(NSDictionary *)userInfo {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 異步獲取一些數據
        // 而後告知獲取數據的結果狀況
        // completionHandler(UIBackgroundFetchResultNewData); // 改成 直接調用
    [application handleBackgroundFetchResult:UIBackgroundFetchResultNewData];
    });
}
複製代碼

對比兩種方式,後者顯然不如在回調 delegate 時直接帶入須要執行的邏輯來得直觀。這種「回調再調用」用法,後來在iOS 10 發佈的系統重構的通知管理框架 UserNotification 中頻繁使用,好比上述方法在 UNUserNotificaionCenterUNUserNotificationCenterDelegate 中的聲明。佈局

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

總結可見,在以下場景中:對象A回調給被回調者B完成後,仍須要被回調者B去調用A並傳遞一些參數(或無參數)執行延續邏輯。採用相似回調一個 block 參數實現回調再調用是很不錯的方案。fetch

3/3 項目實踐

一個使用block作回調處理,並在回調中返回block參數用於延續邏輯在實際項目中的應用案例:

3個項目中,均須要經過網絡接口請求的方式來獲取客服聯繫電話後彈窗提示可撥打,三個網絡接口各不相同。將該業務邏輯封裝爲一個 API ,方便多處業務入口的調用,具體是在通信管理的單例[ContactHelper sharedInstance]:

// 提示撥打客服電話,實例方法
- (void)callCustomerServerInVC:(UIViewController *)VC;
複製代碼

因爲不一樣項目中網絡接口不一致,且接口可能會變更,所以不在ContactHelper寫網絡請求邏輯,而是經過block的形式回調給具體項目進行實現,同時將彈窗的邏輯和樣式封裝在ContactHelper內部進行統一。

  • 第一步,設置獲取客服信息的邏輯,經過ContactHelper的聲明爲 fetcherblock屬性保存,當須要時進行調用
typedef void(^ContactCompletion)(NSDictionary *userInfo, NSString *errorMsg); // 

- (void)configCustomerPhoneFetcher:(void (^)(ContactCompletion completion, UIViewController *vc))fetcher;

/// 保存獲取聯繫方式的邏輯
- (void)configCustomerPhoneFetcher:(void (^)(ContactCompletion))fetcher {
    _fetcher = [fetcher copy]; 
}

/// 項目中具體配置的調用示例
ContactHelper *helper = [ContactHelper sharedInstance];
[helper configCustomerPhoneFetcher:^(ContactCompletion completion, 
                                      UIViewController *vc) {
    // 經過網絡請求異步獲取電話號碼
    NSString *tel = @"400xxxxxxx";
    /// 執行 回調再調用,實現電話號碼撥叫
    completion(@{kContactPhoneKey:tel,nil);
}];
複製代碼
  • 第二步,當業務方調用ContactHelper以彈窗撥打客服電話時,ContactHelper調用第一步配置好的獲取方式 fetcher屬性。
  • 第三步,利用fetcher獲取到並再調用的信息進行彈窗撥號提示,所以ContactHelper內部實現調用客服電話後再彈窗提醒以下:
//獲取客服電話
- (void)callCustomerServiceInVC:(UIViewController *)controller{
    if (!_fetcher)  return;
     // 配置獲取到客服電話後的操做
    ContactCompletion completion = ^(NSDictionary *dic, NSString *errorMsg){
    if (errorMsg) {
        // 提示獲取號碼出錯
    } else {
        NSString *phone = dic[kContactPhoneKey];
       // 彈窗提示撥號
    };

  // 執行保存的回調,並將下一步的操做傳遞過去
   _fetcher(completion, controller);
}
複製代碼

綜上,利用回調再調用這個思路,能夠將3個項目的不一樣接口的請求客服電話的請求隔離在3個項目中設置,而彈窗提示的邏輯則在 ContactHelper中統一處理,而在其餘的一些須要外部獲取數據後再返回到調用者延續執行的狀況均可以使用該方案。

參考文獻

iOS程序犭袁: 有一種 Block 叫 Callback,有一種 Callback 叫 CompletionHandler 其中,在第三方雲服務 LeanCloud 的一些SDK中有相似的高階函數應用。

相關文章
相關標籤/搜索