http://blog.stablekernel.com/blocks-or-delegates/
block和delegate,究竟什么时候选择哪种方式作为回调呢?
当遇到这样的问题时,或许最好的答案是参考Apple的做法.找出Apple在什么情况下使用delegate十分简单:在文档中搜寻delegate
,然后我们会找到大部分使用了delegate的类.
相对而言,找出Apple在什么情况下使用了block的难度会大一点,因为我们不能再文档中寻找^
符号.然而,Apple在遵守命名规则这方面做得很好.例如,一个将NSString类型的对象作为参数的方法的名字中会出现”String”,就像initWithString:
.dataFromString
.
当一个Apple的方法需要一个block作为参数,这个方法的名字将会包含Handler
,Completion
或者Block
.我们可以在文档中搜寻这些关键字,并且得到block的常用方法.
大部分协议包含多个消息
观察GKMatch
类.我们可以发现一下各种情况下都有对应的消息:当接收到另一个播放器的数据,当播放器的状态改变了,当一个错误发生了.这些都是直接的事件.如果Apple在这时使用block,将会有两个选择.第一为每个事件都注册一个block(播放器状态改变block,播放器发生错误blcok等).第二个选择是创建一个接收所有可能输出的block:
1 | void (^matchBlock)(GKMatchEvent eventType, Player *player, NSData *data, NSError *err); |
这种方法既不方便又不直观.所以这种情况永远都不会出现.
每个对象只能有一个同类协议
因为一个对象只能有一个同类协议,它只能对该协议传递消息.观察CLLocationManager
,当这个类找到一个地点时,这个类会告诉一个对象.当然,如果我们需要多于一个对象知道这些信息,我们可以创建另一个location manager.
如果CLLocationManager
是一个单例呢?如果我们不能创建更多的CLLocationManager
实例,我们必须不停地把协议在需要地点信息的对象中各个类中交换.所以,在一个单例中使用协议不是很合理.UIAcclermeter
是一个很好的例子.在iOS的早期版本中,使用单例模式的accelerometer的实例有一个需要不时地转换的协议.因为这个机制十分麻烦,所以在后来的iOS版本中,这个机制被更改了.现在,每个类都可以在CMMotionManager
类中添加block,并且不堵塞其他类接收消息.所以,可以得出一个结论:如果使用了单例,慎重使用协议.
一些协议方法要有返回值
一些协议方法要有返回值,这意味着发出委托的方法需要某些东西的状态.当然,一个block可以保存状态值,或者推断状态值,但是,这更像是一个对象应该做的事,而不是一个block.
想象这样一种情况:如果我们问一个block,”你觉得Bob这个人怎样?”,这个block只能做两件事:一是向被捕获的对象发送消息并且向该对象询问该对象对Bob的看法,第二是返回一个被捕获的值.如果这个block返回的是一个对象的看法,为何不绕过这个block,直接问这个对象呢?如果这个block返回的是一个捕获了的值,为什么不直接从对象得到呢?(这个值很可能是对象的属性).
所以,可以得出一个结论,如果一个回调需要额外的信息,可以考虑使用协议.
过程VS结果
如果我们观察NSURLConnectionDelegate
和NSURLConnectionDataDelegate
,我们可以看到想表达如下内容的消息”我将要开始做这件事了”,”这是我到目前为止所知道的”,”我已经做完这件事了”或者”析构!!!”.这些消息为我们画出了这个对象处理过程的轮廓.
当我观察handler
和completion
方法,我看到的是一个包含了错误信息或者结果的block.这里边并没有任何像”我将要开始做这件事了”的交流.
所以,我们可以得出一个结论:delegate回调更加偏向于面对过程,而block回调更加偏向于面对结果.如果你需要在一个过程的每一步都进行通知,那么使用协议.如果你只是想传递一个结果,那么使用block.
这里是亮点建议.如果需要使用block来进行一个可能会失败的请求,应该使用一个block
1 | [fetcher makeRequest:^(id result) { |
下面这种方法的可读性就好了很多
1 | [fetcher makeRequest:^(id result, NSError *err) { |