使用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
,其模態展現方式,一樣是展現結束後調用completion
的block
代碼。- (BOOL)presentAnimated:(BOOL)animated
completionHandler:(nullable UIPrintInteractionCompletionHandler)completion;
複製代碼
此外,在 WKWebView
中分析JavaScript
代碼時也有相似應用,實際開發中存在 completion
、completionHandler
或者callBack
等不一樣的命名方式,歸根結底目的都是實現一個事件完成後的「回調做用」,與使用「委託模式」的 delegate + protocol
有殊途同歸之妙,這也是不少二級頁面控制器或者視圖的回調流行使用一個 block
屬性來作回調處理的緣由。bash
而相似 UIView 動畫的 animations
的block
動畫參數,以及自動佈局框架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;
} ];
複製代碼
上文說起委託模式也是典型的回調方式之一,在iOS
應用程序的入口就採用了委託模式,即整個應用程序UIApplication
單例的及其委託對象AppDelegate
,UIApplicationDelegate
協議中聲明瞭諸多可選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之內),這個場景就是回調再調用,其中:框架
UIApplication
委託的協議方法,而不是其實例方法,是 UIApplication
調用 AppDelegate
的方法,即回調。block
類型的參數,將一段代碼傳遞給 AppDelegate
,並要求 AppDelegate
完成業務邏輯後執行此block
代碼,以達到調用UIApplication
的目的,即回調再調用。這類將
block
做爲參數或者返回值使用一般稱爲高階函數。異步
這種設計方式,有一種變換的實現方式:由UIApplication
單獨再提供一個 API
讓 AppDelgate
來主動調用,寫一個僞代碼方法以下: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
中頻繁使用,好比上述方法在 UNUserNotificaionCenter
的 UNUserNotificationCenterDelegate
中的聲明。佈局
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void(^)())completionHandler;
複製代碼
總結可見,在以下場景中:對象A
回調給被回調者B
完成後,仍須要被回調者B
去調用A
並傳遞一些參數(或無參數)執行延續邏輯。採用相似回調一個 block 參數實現回調再調用是很不錯的方案。fetch
一個使用block
作回調處理,並在回調中返回block
參數用於延續邏輯在實際項目中的應用案例:
3個項目中,均須要經過網絡接口請求的方式來獲取客服聯繫電話後彈窗提示可撥打,三個網絡接口各不相同。將該業務邏輯封裝爲一個 API ,方便多處業務入口的調用,具體是在通信管理的單例[ContactHelper sharedInstance]
:
// 提示撥打客服電話,實例方法
- (void)callCustomerServerInVC:(UIViewController *)VC;
複製代碼
因爲不一樣項目中網絡接口不一致,且接口可能會變更,所以不在ContactHelper
寫網絡請求邏輯,而是經過block
的形式回調給具體項目進行實現,同時將彈窗的邏輯和樣式封裝在ContactHelper
內部進行統一。
ContactHelper
的聲明爲 fetcher
的block
屬性保存,當須要時進行調用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
中有相似的高階函數應用。