开发该选择Blocks还是Delegates

After my last post, @saambarati asked a great question, which I will paraphrase: “Blocks or delegates? When should I use each for callbacks?”

In these situations, I like to ask myself, “What would Apple do?” And of course, we know what Apple would do, because the documentation itself is a guidebook for design pattern usage if we look at it through a different lens.

We need to find where Apple chooses to use delegates and where it chooses to use blocks. If we find patterns in their choices, we can formalize some rules that will help us make the same decisions in our own code.

Figuring out where Apple uses delegation is pretty simple: search the docs for the term “delegate” and we’ll get most of the classes that use delegation.

Searching for where Apple uses blocks is a little bit more difficult, since we can’t search for the ^ character in the documentation. However, Apple is good about naming conventions when declaring methods (which is such an essential skill for mastery, by the way). For example, a method that wants an NSString as an argument will have “String” in the selector, like initWithString:, dateFromString: or startSpeakingString:.

When an Apple method wants a block, it’ll have “Handler”, “Completion” or simply “Block” in the selector. We can search for these terms in the documentation to build up a reasonable list of block usage in the standard iOS API.

Here are some of my observations:

1. Most delegate protocols have a handful of messages.

I’m looking at GKMatch right now. I see a message for when data is received from another player, when the player changed state, when there is an error and when a player should be re-invited. These are all distinct events. If Apple were to use blocks here, they’d have two options. One, they could register a block for each event. If someone writes a class that does this in Objective-C, they are probably an asshole.

The other option is to just create one block that could receive all possible output:

void (^matchBlock)(GKMatchEvent eventType, Player *player, NSData *data, NSError *err);

This is neither convenient or self-documenting, so you never see this approach. Well, you might see this approach, but there will be so many other eye-gouging lines of code you won’t have the energy to focus on how bad this one is.

Thus, we can say that “If an object has more than one distinct event, use delegation.”

2. An object can only have one delegate.

Since an object can only have one delegate, it can really only talk to that one delegate and nobody else. Let’s look at CLLocationManager here. The location manager will tell one object (and only one object) when a location is found. Of course, if we need more than one object to know about these updates, we’d probably create another location manager.

What if CLLocationManager were a singleton, though? If we couldn’t create any other instances of CLLocationManager, we’d constantly have to be swapping the delegate pointer to whichever object wanted location data. (Or set up some elaborate broadcast system which you - and only you - will understand.) So, it doesn’t make much sense to use delegation for a singleton.

The best example of this is UIAccelerometer. In earlier versions of iOS, the singleton accelerometer instance had a delegate that we had to swap occasionally. This was stupid enough that it was changed in later versions of iOS. Nowadays, any object can attach a block to the CMMotionManager without preventing another object from receiving updates.

Here we can say that, “If an object is a singleton, we can’t use delegation.”

3. Some delegate methods expect a return value

If you look at some delegate methods (and nearly all dataSource methods), there is an expected return value. That means the delegating object is asking for the state of something. While a block could reasonably maintain state or at least deduce state, this is really an object’s role.

Think about it. If I ask a block “What do you think about Bob?” it can only do two things: send a message to a captured object asking what the object thinks about Bob or return a captured value. If it’s returning the response of an object, we should just bypass the block and go right to the object. If it’s returning a captured value, why isn’t that a property on the object?

From this observation, we can say that “If the object is calling back for additional information, we’ll probably use delegation.”

4. Process vs. Results

If I look at NSURLConnectionDelegate and NSURLConnectionDataDelegate, I see messages that say things like “I’m starting to do this,” “Here is what I know so far,” “I’ve finished doing this,” or “DEAR GOD THE WORLD IS ENDING, DEALLOC! DEALLOC! DEALLOC!” These messages outline a process where an interested delegate will want to be informed at each step.

When I look at handler and completion methods, I see a block that contains a response object and an error object. There isn’t any communication for “Here is where I am so far, and I’m still working.”

Thus, we can say that delegate callbacks are more process oriented and blocks are more results oriented. If you need to be informed along the way of a multi-step process, you’ll probably want to use delegation. If you just want the information you are requesting (or details about a failure to get the information), you should use a block. (If you combine this with item 3 in this list, you’ll realize that the delegate can maintain state across all of these events whereas multiple stand-alone blocks could not.)

This brings me to two points I’d like to make. First, if you choose to use blocks for a request that might fail, you should only use one block. I’ve seen code that looks like this:

[fetcher makeRequest:^(id result) {
   // do something with result
} error:^(NSError *err) {
    // Do something with error
}];

That is a lot less readable than this (in my never-humble opinion):

[fetcher makeRequest:^(id result, NSError *err) {
     if(!err) {
         // handle result
     } else {
        // handle error
     }
}];

Of course, I should add that one time someone said to me, “In Smalltalk, we’d do the former, because why use if statements when we should be using objects/blocks?” or a similar such question meant to show off how smart he was. So I showed him this:

[progressBar startAnimating];

[fetcher makeRequest:^(id result) {
   [progressBar stopAnimating];
   // do something with result
} error:^(NSError *err) {
   // WHY ARE YOU TYPING THIS TWICE?!
   [progressBar stopAnimating];
    // Do something with error
}];

Which fulfilled my goal of educating him and his goal of showing off how smart he was.

5. Speed (maybe?)

AVPlayer has a callback for when the current playback time changes. This sounds more like a process than a result, so by observation 4, we’d use delegation. But this particular callback uses a block. I’m guessing this is for speed - since this block can be called, theoretically, hundreds of times per second, the message lookup might be slow.

When it doubt, doc it out

I honestly could not think of a good summation header, so you’ve been stuck with this one. (Also, I just sat down to eat my lunch and it seemed like a Five Guys burger was more important than being clever.) This post should give you some good guidelines for implementing callbacks in your own classes. If your case isn’t covered, I bet finding a similar class in the iOS API will answer your question. And if you are doing something so radical that no one has done it before, try both and see what works.

原文:
http://blog.stablekernel.com/blocks-or-delegates/
译文:

开发该选择Blocks还是Delegates

有人问了我一个很棒的问题,我把这个问题总结为:“开发过程中该选择 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,那么可能会有以下两种解决方式:

1.可以对应每一个事件注册相应的block,显然这种方式是不合理的。( If someone writes a class that does this in Objective-C, they are probably an asshole.)

2.创建一个可以接受任何可能输入的block

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。我们可以看到如下的代码:

[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
     }
}];

当然,我需要补充一下。有一个人问我:在Smalltalk中,我们用前者。因为为什么当我们用if语句的时候,我们就一定要用object或者blocks?。或者相类似的问题来炫耀他多机智。所以,我向他展示了这个。

[progressBar startAnimating];

[fetcher makeRequest:^(id result) {
   [progressBar stopAnimating];
   // do something with result
} error:^(NSError *err) {
   // WHY ARE YOU TYPING THIS TWICE?!为什么调用两次呢?
   [progressBar stopAnimating];
    // Do something with error
}];

5 速度快(也许)

AVPlayer 需要回调进度的变化。这挺起来更像是过程而不是结果。因为根据观察4,我们应该是delegation。但是这次用的是block。但是这次特殊些,回调用的是block。我猜应该是速度快。由于这个block理论上能每秒钟调用上百次,所以,消息的查找会变慢

原文:
http://ios.jobbole.com/82703/

摘录:

1 because the documentation itself is a guidebook for design pattern usage if we look at it through a different lens

因为从某一层面上看,Apple的文档就是一本用来指导我们如何使用设计模式的指导书

2.If we find patterns in their choices, we can formalize some rules that will help us make the same decisions in our own code

如果我们发现了Apple做这种选择的套路,我们就可以构建出一些规则,可以帮助在我们在自己的代码中做相同选择

3 These are all distinct events

这些都是不同的事件

4 they could register a block for each event.If someone writes a class that does this in Objective-C, they are probably an asshole

可以对应每一个事件注册相应的block,显然这种方式是不合理的。

5 This is neither convenient or self-documenting

这种方式既不简便又不易读

6.Since an object can only have one delegate, it can really only talk to that one delegate and nobody else

由于一个对象只能有一个delegate,它只能与这个delegate通信。

7 What if CLLocationManager were a singleton, though?

如果CLLocationManager是个单例呢?

8.we’d constantly have to be swapping the delegate pointer to whichever object wanted location data. (Or set up some elaborate broadcast system which you - and only you - will understand.)

我们必须不断地切换delegate指针到需要地理数据的对象上(或者创建一个只有你理解的精密的广播系统)

9 it doesn’t make much sense to use delegation for a singleton

delegatetion在单例上没有多大意义

10 While a block could reasonably maintain state or at least deduce state, this is really an object’s role

而一个block则可以合理地包含state或者至少是推断state,因此block真正是对象的一个属性

11.send a message to a captured object asking what the object thinks about Bob or return a captured value.

发送一个消息去捕获对象并询问这个对象怎么看待Bob,或者直接返回一个捕获的值

12 These messages outline a process where an interested delegate will want to be informed at each step

这些消息组成一个流程,而那些对流程感兴趣的delegate将会在每一步得到相应的通知

13 we can say that delegate callbacks are more process oriented and blocks are more results oriented.

delegate的回调更多的面向过程,而block则是面向结果的

14.This brings me to two points I’d like to make

从上面我们可以得出两个关键点

15.That is a lot less readable than this

上面代码的可读性明显比下面block的可读性差
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值