Blocks or Delegation

After my last post, @saambarati asked a great question, which I will paraphrase: “When should I use blocks instead of delegation 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 delegation 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.

5 thoughts on “Blocks or Delegation

  1. Pingback: [iOS] Objective-C , block | Monster Oasis

  2. Pingback: iOS 7 Block Programming Resources

  3. Pingback: My Favourite iOS Tutorials | Anoop4Real

  4. joeconway Post author

    1. Let’s first clarify that the delegate pattern is a callback pattern. I will assume when you say “callback”, you mean using a block. UIAlertView does not support the block, the link you referenced is simply someone adding block callbacks to UIAlertView via categories.

    2. Again, assuming “callback” to mean “block callback” here. NSURLConnection uses both, and so does the new class that replaces NSURLConnection in iOS 7. This is a convenience for a very complicated class. Most developers will only ever need the data and whether or not the call succeeded or failed, so the block based API exists. For those that need much more support (like redirection), they could opt-in to the delegate callback pattern.

    3. You are right, it shouldn’t be the deciding factor. However, looking at something like NSXMLParserDelegate, it would be cumbersome to pack all 20 some of those messages into a single block callback. It’s really about what makes the code in your controller the most clear.

    Reply
  5. Kunal

    Nice blog. I have a few questions though !!

    1)If only singleton classes should use callbacks then why UIAlertview has callbacks too ?? Apple should have restricted to delegates but they do support callbacks (http://www.abdus.me/ios-programming-tips/uialertview-with-blocks-call-back/)

    2)AFNetworking Library uses callbacks for success and failure of an HTTP request but they could have used delegates instead (like NSURLconnection). People prefer to use callbacks (look at his sample codes).

    3) Do you think number of messages is really a criteria. You can always encapsulate that information to reduce params (I might be wrong here in terms of design patterns)?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>