什么时候该用Blocks?什么时候该用Delegate?
即使block 可以大幅度取代delegate 处理callback,但是从苹果自己的API 设计中可以看到,并不是所有的delegate 都被block 取代,在Cocoa 与Cocoa Touch framework 中,仍然大幅度使用delegate。那么,我们就要问:当我们在设计API 的时候,什么状况下应该使用block?什么时候又该使用delegate?
通常的区分方式是:如果一个method 或function 的调用只有单一的callback,那么就使用block,如果可能会有多个不同的callback,那么就使用delegate。
这么做的好处是:当一个method 或function 呼叫会有多种callback 的时候,很有可能某些callback 是没有必要实作的。
如果使用delegate实作,那么,在delegate需要实作的protocol中,我们可以用@required
与@optional
关键字区分哪些是一定需要实作的delegate method。
但相对的,用block处理callback,就会很难区分某个block是否是必须要实作:在Xcode 6.3之前,Objective-C并没有nullable
、nonnull
等关键字,让我们知道某个property、或某个method要传入的block可不可以是nil,我们也往往搞不清楚在这些地方传入nil,会不会发生什么危险的事情。1
举个例子。在iOS 7 之后,苹果鼓励开发者使用NSURLSession 处理网路连线, NSURLSession 就充分表现了「单一callback 用block、多重callback 用delegate」这一点。
假如我们现在想要把KKBOX 的官网首页抓下来,我们只要建立一个NSURLSessionDataTask 实例,一般来说,我们只需要处理「这个网络连接做完事情的下一步该做什么」,所以一般也只需要实作这个task 的completion handler,就是传入网路链接结束之后要执行的block;一般连线结束,大概就是成功抓到资料或是连线失败两种状况,所以我们可以透过data 与error这两物件判断是哪种状况:失败的话,error 就不会是nil,我们就要处理error,反之就要处理data。
NSURL *URL = [NSURL URLWithString:@"http://kkbox.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
// handle error
return;
}
// handle data
}];
[task resume];
但,NSURLSession 本身也还是具有delegate。我们在发送网络连接的时候,除了处理连接结束要做什么之外,有时候也可能会想处理在连线中途所发生的其他状况,像是:HTTP 连线收到302 转址、遇到有问题的SSL 凭证、server 要求用户输入帐号密码,这些状况我们要不要提示使用者?或,如果这是一个传递大档、很花时间的连线,我们有没有必要显示连线进度条?这些状况还是会传递给NSURLSession 的delegate,而如果我们要处理这些状况,就要实现以下这些delegate methods。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
1 .其实是把Swift的语言特性移植回Objective-C。 ↩