Fundamentals of Callbacks for Swift Developers

Updated on October 12, 2016 – Swift 3.0

Callbacks: What are they? How do they work? What are they used for in practice?

My goal in this article is to provide answers to these questions so that you have a foundational understanding of this common programming pattern in iOS development.

What are callbacks?

Let’s approach the definition from a “big picture” scenario:

When we’re building software, we’re either using APIs, or building APIs, are we not? We’re either using code that “hooks into” what other developers have designed and made available to us, or we’re creating code that other code will “hook into” and interact with, even if the “other code” is written by us in our own app.

Learn by example: Designing an API for callbacks

Since this is the case, let’s put on the “API Designer” hat for a moment and suppose that we’re working to create a hypothetical Type called an ImageSketcher. One of the functions of ImageSketcher is called sketch() (parameters omitted for the moment). It will allow developers to pass it an image resource, such as a JPEG or a PNG, as one of its arguments. The function will then proceed to generate an animated sketch of that PNG for the user to view.

In order to do the work of generating the animated sketch, sketch() needs to do a lot of crunching. I have no idea what it’d take to do this in real life, honestly – let’s just work on the premise that it’ll take a few seconds to generate the animation so the end-user can watch it when it’s finished.

In situations like this, it’d be nice to design ImageSketcher where the start and end of the process are decoupled:

Pass off the image. Let it do its thing to generate the animation. When it’s finished, “hook back in” and respond to the knowledge that the animation generation is complete. At that point, we could ask the end-user, “Hey, your sketch is done! Want to watch it now?”

This particular example centers on a strategy that uses “asynchronous programming” techniques. It’s often done to boost app performance and/or responsiveness.

During that middle part where we’re “disconnected” from the ImageSketcher's sketch() function, control of the app wouldn’t be tied up. Folks could continue to interact with the app.

From a developer’s point of view, he/she can program against the API by calling the function, knowing that at [some unknown point in the future], it will finish, and that he/she will have the opportunity at that time to “hook back in” and respond to that completion event.

That last bit is critical. Giving other developers the opportunity to re-insert themselves with custom application logic when the asynchronous task ends is very important as an API designer.

Exactly what you as an API designer communicate back to the caller of your API is up to you, but put yourself in the client developer’s shoes for a moment:

Wouldn’t it be nice to know if something went wrong, or if data (the completed animation, for example) came out of that sketch()'s work? That’s exactly the kind of information that we’d expect an API designer would provide us with this completion event.

So… just what are the options could we give callers of this method to “hook-in” and know that the work is done?

“Hook-in” options

In scenarios like this, Swift developers have about 3 options to choose from:

So callbacks are used as another way for one piece of code to communicate with another piece of code somewhere else in the app.

How do callbacks work?

Here is a brief overview of the communication interaction using our hypothetical ImageSketcher as a working example:

  1. An API designer has created the sketch(image:completion:) function, and has chosen to accept a completion “callback” as the means of communicating the fact that the animation has been generated and is ready to show the end-user.

  2. Data, such as the completed sketch animation instance will be delivered through the completion callback’s parameter(s). The completion parameter of our sketch() function will have a signature that client developers must adhere to in order to facilitate the delivery of that data.

  3. A client developer writes up a routine (a function or closure) and passes it as the completion parameter’s argument. The function/closure that he/she writes will have a list of parameters that matches up to what the API designer required.

  4. When sketch() is finished generating the sketch, the designer of the API has programmed his/her function to call the callback that you pass in. The API designer will pass along any data that was generated as arguments to the callback function’s parameters.

  5. The client developer’s callback logic executes.

Callbacks are functions that often take the form of a closure (basically an in-line function with no name that’s passed as a parameter to another function), but they could technically be a named function.

Perhaps it’s easiest to see in code itself. Here’s a skeleton view of what that looks like:

 1// API Designer World
 2struct SketchAnimation {
 3    // represents some fully-generated animation that's ready to play by the end user
 4}
 5
 6struct ImageSketcher {
 7    func sketch(image: UIImage, completion: (_ sketchAnimation: SketchAnimation) -> Void) {
 8        // do some crunching to create the SketchAnimation instance...
 9        let animation = SketchAnimation()
10        
11        // invoke the completion callback
12        // pass along the completed sketch animation instance
13        completion(animation)
14    }
15}
16
17// ---------------------------------------------------------------------
18
19// Client Developer World
20class MainViewController: UIViewController {
21    // ...
22    
23    // end-user interacts with the app somehow to create an image sketch animation
24    // when they do, this function is called...
25    func createSketchAnimation(imageToSketch: UIImage) {
26        let sketcher = ImageSketcher()
27        
28        sketcher.sketch(image: imageToSketch, completion: {(animation: SketchAnimation) -> Void in
29            // This is the callback.  It's a closure, passed as the argument to the sketch function's completion parameter
30            
31            // Ask the end-user if they'd like to view the completed animation now...
32            // You as a develoepr have access to the completed animation through the animation parameter to this closure
33        })
34    }
35    
36    // ...
37}

You’ll notice a couple of things…

First, I’ve separated the two “worlds” that exist: “API Designer World” and “Client Developer World”. Hopefully seeing both in action can give you the most complete picture of what’s going on with callbacks.

In “API Designer World”, we’ve got the ImageSketcher and its implementation.

In “Client Developer World”, we’ve got someone using an instance of ImageSketcher.

Second, notice the interaction. As an API designer, I was thinking, “Hey, when my sketching process is complete, I want to let the caller know that it’s finished and hand them the completed SketchAnimation instance. To do that, I’ll need them to pass me a function that I can hand it off to via a parameter”.

As a client developer, I’m thinking, “Okay, I’m going to call sketch(), but how am I going to know when it’s done and how will I get the animation? Oh! I see – I need to give it a completion closure (a callback), and they’ll hand me the completed SketchAnimation instance through my closure’s parameter. Sweet!”

I’m hoping the “thinking out loud” here helps you piece it together.

Examples from the iOS SDK

So how about a few real examples, say, from the iOS SDK. Where are callbacks used there?

UIAlertController

A really simple example of callbacks being used in the wild is when we work with UIAlertControllers. Take a look at this example:

 1let alertController = UIAlertController(title: "My Alert", message: "A Message", preferredStyle: UIAlertControllerStyle.alert)
 2
 3let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(action: UIAlertAction) -> Void in
 4    // Do something based on the user tapping this action button
 5    
 6    // Notice that we get an instance of the UIAlertAction that was tapped if we need it
 7})
 8
 9alertController.addAction(OKAction)
10
11self.present(alertController, animated: true, completion: nil)
12// We could have provided a completion callback here, too,
13// but we didn't need to respond to the view controller's presentation, so we passed nil

So the UIAlertAction is actually the thing that takes the callback (the handler parameter). There’s also an example on a View Controller’s present() function. Both are intended to communicate something back to the caller.

In the case of the UIAlertAction, the handler will be the logic to handle the user’s tapping on that specific alert button.

In the case of the present call, Apple has given us the opportunity to “hook in” to the presentation event and know when it’s complete, in case we need to perform additional logic at that moment.

URLSession

The world of HTTP is inherently asynchronous, so you’d expect to see some kind of pattern employed to deal with the “disconnectedness” of the start and finish of a process, such as making an HTTP request.

URLSession encapsulates certain HTTP actions, such as retrieving the contents of a URL, in instances called URLSessionDataTask. How does it communicate the fact that the HTTP request is complete, along with the data contained in the response? You guessed it: A callback.

Take a look at this function signature from the Apple Developer Documentation on URLSession:

1func dataTask(with url: URL, 
2              completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

The completionHandler parameter of this function is the interface that the API designers have created for delivering the resulting payload of the HTTP request when it’s finished and ready to hand off for further processing.

Client developers of this API will be expected to make the call to dataTask(with:completionHandler:) and supply it a completion callback to know when things are complete.

Animations

You’ll see all kinds of completion callbacks sprinkled throughout some of the simpler iOS animation APIs.

If you take a look at the following function signatures from the Apple Developer Documentation on UIViews, you’ll see the completion parameters to many of these functions:

1transition(with:duration:options:animations:completion:)
2transition(from:to:duration:options:completion:)
3animateKeyframes(withDuration:delay:options:animations:completion:)
4addKeyframe(withRelativeStartTime:relativeDuration:animations:)
5perform(_:on:options:animations:completion:)
6animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)

Wrapping Up

Believe it or not, the usage of callbacks is one of the less-complicated ways of communicating between parts of code.

My goal in this article was to show all of the sides and perspectives and players to give you insight into how this communication takes place.

Now that the foundations are laid, it is my hope that you’ll be able to more confidently use callbacks and know what’s happening as you encounter them in your Swift code!

comments powered by Disqus