【介绍】iOS10推送部分的API大量使用了CompletionHandler的命名方式。在这篇文章中,我们将通过对比这个Block的特殊性来更好地理解它。并在自己的项目中实现Blcok的CompletionHandler风格。当我们作为开发者在集成一个Lib(也叫轮子、SDK,以下统称Lib)时,会发现我们遇到的block按照功能可以分为这几种:Lib通知开发否则,库操作已经完成。一般称为Callback开发者通知Lib开发者操作已经完成。一般可以命名为CompletionHandler。这两个地方的区别:前者是“区块执行”,后者是“区块填充”。CallbackvsCompletionHandler在命名和功能上的区别苹果公司并没有明确指出,但是如果按照“执行和填充”的功能来划分,那么callback和completionHandler的命名可以分开处理。同时也方便调用者了解block的功能。但一般来说,在苹果官方的命名中,“Blockfilling”函数一般命名为“completionHandler”,“Blockexecution”函数大多命名为“callback”,小部分命名为“completionHandler”。例如:在NSURLSession中,以下函数将“callback”命名为“completionHandler”:-(NSURLSessionDataTask*)dataTaskWithURL:(NSURL*)urlcompletionHandler:(void(^)(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror))完成处理程序;我们经常看到CompletionHandler用于第一种场景,第一种场景“Blockexecution”命名为Callback更合适。不是所有的Block都适合称为CompletionHandler一般情况下,CompletionHandler的设计往往会考虑到多线程操作,所以可以完全异步操作,在线程结束时再执行CompletionHandler。下面的例子将以多种方式描述CompletionHandler是如何工作的。线程场景中的一些优势。CompletionHandler+Delegate组合在iOS10新增的UserNotificaitons中大量使用了这种Block,例如:-(void)userNotificationCenter:(UNUserNotificationCenter*)centerdidReceiveNotificationResponse:(UNNotificationResponse*)responsewithCompletionHandler:(void(^)(void))completionHandler;文档对completionHandler的注释是这样的:Theblocktoexecutewhenyouhavefinishedprocessingtheuser'sresponse.Youmustexecutethisblockfromyourmethodandshouldcallitasquicklyaspossible.Theblockhasnoreturnvalueorparameters.同样在这里也有应用:-(void)URLSession:(NSURLSession*)sessiontask:(NSURLSessionTask*)taskdidReceiveChallenge:(NSURLAuthenticationChallenge*)challengecompletionHandler:(void(^)(NSURLSessionAuthChallengeDispositiondisposition,NSURLCredential*__nullablecredential))completionHandler;还有一个很常见的例子(Delegate模式下使用URLSession时四个基本代理函数之一)-(void)URLSession:(NSURLSession*)sessiondataTask:(NSURLSessionDataTask*)dataTaskdidReceiveResponse:(NSURLResponse*)responsecompletionHandler:(void(^)(NSURLSessionnResponseDispositiondisposition))completionHandler;在proxy方法的实现代码中,如果completionHandler(NSURLSessionResponseAllow)没有被执行,http请求就会被终止。CompletionHandler+Block组合函数使用函数作为参数或返回值,称为高阶函数。按照这个定义,Block以Block为参数,是一个高阶函数。下面结合实际应用场景来看一个例子:如果有这样的需求:以我之前的IM项目ChatKit-OC(开源,以下简称ChatKit)为例,当你的应用要集成一个IM服务,你可能这时候你的APP已经上架了,并且有自己的注册、登录等流程。使用ChatKit聊天非常简单,只需给ChatKit一个id即可。聊天正常,但是双方只能看到一个id,体验很??差。但是如何显示头像和昵称呢?所以我们设计了这样一个接口,-setFetchProfilesBlock:。这是上层(APP)提供用户信息的block,因为ChatKit不关心业务逻辑信息,比如用户昵称,用户头像等,用户可以通过ChatKit单例向ChatKit注入一个用户信息内容提供block,通过这个用户信息提供块,ChatKit可以正确的绘制业务逻辑数据。示意图如下:具体实现如下:方法定义如下:/*!*@briefTheblocktoexecutewiththeusers'informationfortheuserIds.Alwaysexecutethisblockatsomepointwhenfetchingprofilescompletesonmainthread.Specifyusers'informationhowyouwantChatKittoshow.*@attentionIfyoufetchusersfails,youshouldreturenil,meanwhile,givetheerrorreason.*/typedefvoid(^LCCKFetchProfilesCompletionHandler)(NSArray>*users,NSError*error);/*!*@briefWhenLeanCloudChatKitwantstofetchprofiles,thisblockwillbeinvoked.*@paramuserIdsUserids*@paramcompletionHandlerTheblocktoexecutewiththeusers'informationfortheuserIds.Alwaysexecutethisblockatsomepointduringyourimplementationofthismethodonmainthread.Specifyusers'informationhowyouwantChatKittoshow.*/typedefvoid(^LCCKFetchProfilesBlock)(NSArray*userIds,LCCKFetchProfilesCompletionHandlercompletionHandler);@property(nonatomic,copy)LCCKFetchProfilesBlockfetchProfilesBlock;/*!*@briefAddtheablititytofetchprofiles.*@attentionYoumustgetpeerinformationbypeeridwithasynchronousimplementation.*Ifimplemeted,这个块将由LeanCloudChatKit自动调用以获取对等配置文件。*/-(void)setFetchProfilesBlock:(LCCKFetchProfilesBlock)fetchProfilesBlock;用法如下:#warning注意:必须实现setFetchProfilesBlock方法,否则ChatKit将无法显示用户头像和用户昵称下面方法循环模拟通过userIds同步查询用户信息的过程,需要换成AppAPI同步查询[[LCChatKitsharedInstance]setFetchProfilesBlock:^(NSArray*userIds,LCCKFetchProfilesCompletionHandlercompletionHandler){if(userIds.count==0){NSIntegercode=0;NSString*errorReasonText=@"Useridsisnil";NSDictionary*errorInfo=@{@"code":@(code),NSLocalizedDescriptionKey:errorReasonText,};NSError*error=[NSErrorerrorWithDomain:NSStringFromClass([selfclass])code:codeuserInfo:errorInfo];!completionHandler?:completionHandler(nil,error);return;}NSMutableArray*users=[NSMutableArrayarrayWithCapacity:userIds.count];#warning注意:下面的方法模拟了通过userIds同步查询用户信息的过程,需要这里要替换App的API的同步查询eWithFormat:@"peerIdlike%@",clientId];//这里的LCCKContactProfiles和LCCKProfileKeyPeerId都是预定义的宏定义,NSArray*searchedUsers=[LCCKContactProfilesfilteredArrayUsingPredicate:predicate];if(searchedUsers.count>0){NSDictionary*user=searchedUsers[0];NSURL*avatarURL=[NSURURRLWithString:user[LCCKProfileKeyAvatarURL]];LCCKUser*user_=[LCCKUseruserWithUserId:user[LCCKProfileKeyPeerId]name:user[LCCKProfileKeyName]avatarURL:avatarURLclientId:clientId];[usersaddObject:user_];}else{//注意:如果网络请求失败,请至少提供ClientId!LCCKUser*user_=[LCCKUseruserWithClientId:clientId];[usersaddObject:user_];}}];//模拟网络延时,3秒//sleep(3);#warningImportant:completionHandler这个Bock是必须执行的,它需要在你的**获取到用户信息**后,将信息传递给Block!!completionHandler?:completionHandler([userscopy],nil);}];对于上面Fetch方法的应用场景,utility方法的返回值也可以实现,但是和CompletionHandler相比,不能自由切换线程是劣势
