leitingdulante 2015-09-25
有人问了我一个很棒的问题,我把这个问题总结为:“开发过程中该选择 blocks or delegates?当我们需要实现回调的时候,使用哪一种方式比较合适呢?”
一般在这种情况下,我喜欢问我自己:“如果问题交给Apple,他会怎么做呢?”当然,我们都知道Apple肯定知道怎么做,因为从某一层面上看,Apple的文档就是一本用来指导我们如何使用设计模式的指导书。
因此我们需要去研究一下Apple分别是在什么情况下使用delegate和block,如果我们发现了Apple做这种选择的套路,我们就可以构建出一些规则,可以帮助在我们在自己的代码中做相同选择。
要找出Apple使用delegate的场景很简单,我们只要搜索官方文档中的“delegate”,就会获取到很多使用delegation的类。
但是搜索Apple中有关使用blocks的文档就有点困难了,因为我们不能直接搜索文档中的“^” 。然而,Apple声明方法时有很好的命名习惯(这也是我们精通iOS开发的一项必备技能)。例如:一个以NSString为参数的方法,方法的selector就会有String字眼,像initWithString;dateFromString;StartSpeaingString。
当Apple的方法使用block,这个方法将会有“Handler”,“Completion”或者简单的“Block”作为selector;因此我们可以在标准的iOS API文档中搜索这些关键词,用以构建一个可信任的block用例列表。
1.大多数delegate protocols 都拥有几个消息源。
以我正在看的GKMatch为例(A GKMatch object provides a peer-to-peer network between a group of devices that are connected to Game Center,是iOS API中用来提供一组设备连接到Game Center点对点网络的对象)。从这个类中可以看到消息的来源分别是:当从其他玩家那接收到数据、当玩家切换了状态、当发生错误或者当一个玩家应该被重新邀请。这些都是不同的事件。如果Apple在这里使用block,那么可能会有以下两种解决方式:
可以对应每一个事件注册相应的block,显然这种方式是不合理的。( If someone writes a class that does this in Objective-C, they are probably an asshole.)
创建一个可以接受任何可能输入的block
1
void (^matchBlock)(GKMatchEvent eventType, Player *player, NSData *data, NSError *err);
很明显这种方式既不简便又不易读,所以你可能从未看过这样的解决方案。如果你看过这样的解决方式,但是这显然是一个糟糕至极的代码行,你不会有精力去维护这个。
因此,我们可以得出一个结论:如果对象有超过一个以上不同的事件源,使用delegation。
2.一个对象只能有一个delegate
由于一个对象只能有一个delegate,而且它只能与这个delegate通信。让我们看看CLLocationManager 这个类,当发现地理位置后,location manager 只会通知一个对象(有且只有一个)。当然,如果我们需要更多的对象去知道这个更新,我们最好创建其他的location manager。
这里有的人可能想到,如果CLLocationManager是个单例呢?如果我们不能创建CLLocationManager的其他实例,就必须不断地切换delegate指针到需要地理数据的对象上(或者创建一个只有你理解的精密的广播系统)。因此,这样看起来,delegatetion在单例上没有多大意义。
关于这点,最好的印证例子就是UIAccelerometer。在早期版本的iOS中,单例的 accelerometer 实例有一个delegate,导致我们必须偶尔切换一下。这个愚蠢的问题在之后的IOS版本被修改了,现在,任意一个对象都可以访问CMMotionManager block,而不需要阻止其他的对象来接收更新。
因此,我们可以得出另一个结论:“如果一个对象是单例,不要使用delegation”。
3.一般的delegate方法会有返回值
如果你观察一些delegate方法(几乎所有的dataSource方法)都有一个返回值。这就意味着delegating对象在请求某些东西的state(对象的值,或者对象本身),而一个block则可以合理地包含state或者至少是推断state,因此block真正是对象的一个属性。
让我们思考一下一个有趣的场景,如果向一个block提问:“What do you think about Bob?”。block可能会做两件事情:发送一个消息去捕获对象并询问这个对象怎么看待Bob,或者直接返回一个捕获的值。如果返回了一个对象的响应,我们应该越过这个block直接获取这个对象。如果它返回了一个捕获的值,那么这应该是一个对象的属性。
从以上的观察,我们可以得出结论:如果对象的请求带有附加信息,更应该使用delegation
4.过程 vs 结果(Process vs. Results)
如果查看NSURLConnectionDelegate 以及 NSURLConnectionDataDelegate,我们在可以protocol中看到这样的消息:我将要做什么(如: willSendRequest,将要发送请求)、到目前为止我知道的信息(如:canAuthenticateAgainstProtectionSpace)、我已经完成这些啦( didReceiveResponse,收到请求的回复,即完成请求)。这些消息组成一个流程,而那些对流程感兴趣的delegate将会在每一步得到相应的通知。
当我们观察handler和完整的方法时,我们发现一个block包含一个响应对象和一个错误对象。显然这里没有任何有关“我在哪里,我正在做什么的”的交互。
因此我们可以这样认为,delegate的回调更多的面向过程,而block则是面向结果的。如果你需要得到一条多步进程的通知,你应该使用delegation。而当你只是希望得到你请求的信息(或者获取信息时的错误提示),你应该使用block。(如果你结合之前的3个结论,你会发现delegate可以在所有事件中维持state,而多个独立的block确不能)
从上面我们可以得出两个关键点。首先,如果你使用block去请求一个可能失败的请求,你应当只使用一个block。我们可以看到如下的代码:
1
2
3
4
5
[fetcher makeRequest:^(id result) {
// do something with result
} error:^(NSError *err) {
// Do something with error
}];