When Should I Use Blocks and Closures or Delegates for Callbacks?

This blog piece was updated on 10/10/2016 to include updated code from Objective-C to Swift. This post was originally published on 7/11//2013.

After my last post, @saambarati asked a great question, which I will paraphrase: “Closures 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 closures. 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 closures is a little bit more difficult. 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 a String as an argument will have “String” in the selector, like initWithString:, dateFromString: or startSpeakingString:.

When an Apple method wants a closure, it’ll have “Handler” or “Completion”. 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 closures here, they’d have two options. One, they could register a closure for each event. If someone writes a class that does this in Swift, they are probably an asshole.

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

var matchBlock: (_ eventType: GKMatchEvent, _ player: GKPlayer?, _ data: Data?, _ error: Error?) -> Void

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 closure 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 closure could reasonably maintain state or at least deduce state, this is really an object’s role.

Think about it. If I ask a closure “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 closure 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 closure 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 closure 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 closure. (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 closures could not.)

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

fetcher.makeRequest({ (result) in
// Do something with result
}, error: { (error) in
// Do something with error
})

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

fetcher.makeRequest { (result, error) in
    if let result = result {
        // 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/closures?” or a similar such question meant to show off how smart he was. So I showed him this:

progressBar.startAnimating()

fetcher.makeRequest({ (result) in
progressBar.stopAnimating()
// Do something with result
}, error: { (error) in
// 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 closure. I’m guessing this is for speed – since this closure 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.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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