【OC】开发中选择delegate 还是 block
By Joe Conway,CEO | Jul 11,2013 11:29:00 AM
译者注:译文已在 CocoaChina
上发布。
前文:网络上找了很多关于delegation和block的使用场景,发现没有很满意的解释,后来无意中在stablekernel找到了这篇文章,文中作者不仅仅是给出了解决方案,更值得我们深思的是作者独特的思考和解决问题的方式,因此将这篇文章翻译过来,和诸君探讨,翻译的很多地方不是很到位,望大家提出意见建议。
有人问了我一个很棒的问题,我把这个问题总结为:“开发过程中该选择 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
用例列表。
大多数 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
void (^matchBlock)(GKMatchEvent eventType, Player *player, NSData *data, NSError *err);
很明显这种方式既不简便又不易读,所以你可能从未看过这样的解决方案。如果你看过这样的解决方式,但是这显然是一个糟糕至极的代码行,你不会有精力去维护这个。
因此,我们可以得出一个结论:如果对象有超过一个以上不同的事件源,使用 delegation
。
一个对象只能有一个 delegate
由于一个对象只能有一个 delegate
,而且它只能与这个 delegate
通信。让我们看看 CLLocationManager
这个类,当发现地理位置后, location manager
只会通知一个对象(有且只有一个)。当然,如果我们需要更多的对象去知道这个更新,我们最好创建其他的 location manager
。
这里有的人可能想到,如果 CLLocationManager
是个单例呢?如果我们不能创建 CLLocationManager
的其他实例,就必须不断地切换 delegate
指针到需要地理数据的对象上(或者创建一个只有你理解的精密的广播系统)。因此,这样看起来, delegatetion
在单例上没有多大意义。
关于这点,最好的印证例子就是 UIAccelerometer
。在早期版本的 IOS
中,单例的 accelerometer
实例有一个 delegate
,导致我们必须偶尔切换一下。这个愚蠢的问题在之后的 IOS
版本被修改了,现在,任意一个对象都可以访问 CMMotionManager block
,而不需要阻止其他的对象来接收更新。
因此,我们可以得出另一个结论:“如果一个对象是单例,不要使用 delegation
”
一般的 delegate
方法会有返回值
如果你观察一些 delegate
方法(几乎所有的 dataSource
方法)都有一个返回值。这就意味着 delegating
对象在请求某些东西的state(对象的值,或者对象本身),而一个 block
则可以合理地包含 state
或者至少是可以推断 state
,因此block
真正是对象的一个属性。
让我们思考一下一个有趣的场景,如果向一个 block
提问: What do you think about Bob?
。 block
可能会做两件事情:发送一个消息去捕获对象并询问这个对象怎么看待 Bob
,或者直接返回一个捕获的值。如果返回了一个对象的响应,我们应该越过这个 block
直接获取这个对象。如果它返回了一个捕获的值,那么这应该是一个对象的属性。
从以上的观察,我们可以得出结论:如果对象的请求带有附加信息,更应该使用 delegation
过程 vs 结果( Process vs. Results
)
如果查看 NSURLConnectionDelegate
以及 NSURLConnectionDataDelegate
,我们在可以 protocol
中看到这样的消息:我将要做什么(如: willSendRequest
,将要发送请求)、到目前为止我知道的信息(如: canAuthenticateAgainstProtectionSpace
)、我已经完成这些啦( didReceiveResponse
,收到请求的回复,即完成请求)。这些消息组成一个流程,而那些对流程感兴趣的delegate将会在每一步得到相应的通知。
当我们观察 handler
和完整的方法时,我们发现一个 block
包含一个响应对象和一个错误对象。显然这里没有任何有关“我在哪里,我正在做什么的”的交互。
因此我们可以这样认为, delegate
的回调更多的面向过程,而 block
则是面向结果的。如果你需要得到一条多步进程的通知,你应该使用 delegation
。
而当你只是希望得到你请求的信息(或者获取信息时的错误提示),你应该使用block。(如果你结合之前的三个结论,你会发现 delegate
可以在所有事件中维持 state
,而多个独立的 block
确不能)
从上面我们可以得出两个关键点。首先,如果你使用 block
去请求一个可能失败的请求,你应当只使用一个 block
。我们可以看到如下的代码:
[fetcher makeRequest:^(id result) {
// do something with result
} error:^(NSError *err) {
// Do something with error
}];
上面代码的可读性明显比下面 block
的可读性差(作者说这个是他个人不谦虚的观点,译者认为没有那么严重)
[fetcher makeRequest:^(id result, NSError *err) { if(!err) { // handle result } else { // handle error } }];