Completion Handlers in Swift
November 16, 2015 - Updated: November 10, 2016 - Swift 3.0

We’ve been making lots of API calls using Alamofire and dataTask(with request:), like in Simple REST API Calls with Swift. And we keep using completion handlers but we’ve never really looked at them carefully to figure out just what they’re doing. Let’s sort that out today.

To make quick async URL requests in Swift we can use this function:


open func dataTask(with request: URLRequest, 
  completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void
  ) -> URLSessionDataTask

If you’re not familiar with it, there’s lots about it in Simple REST API Calls with Swift. In short, it makes a URL request and, once it gets a response, lets us run code to handle whatever it got back: possibly data, a URLResponse, and/or an error. The completion handler is the code that we get to provide to get called when it comes back with those items. It’s where we can work with the results of the call: error checking, saving the data locally, updating the UI, whatever.

Simple API Call

Here’s how we use a simple URL request to get the first post from JSONPlaceholder, a dummy API that happens to have todo objects:

First, set up the URL request:


let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoEndpoint) else {
  print("Error: cannot create URL")
  return
}
let urlRequest = URLRequest(url: url)

The guard statement lets us check that the URL we’ve provided is valid.

Then we need a URLSession to use to send the request:


let session = URLSession.shared

Then create the data task:


let task = session.dataTask(with: urlRequest, completionHandler:{ _, _, _ in })

And finally send it (yes, this is an oddly named function):


task.resume()

So that’s how to make the request. How do we do anything with the results?

What’s a Completion Handler?

Completion handlers can be a bit confusing the first time you run in to them. On the one hand, they’re a variable or argument but, on the other hand, they’re a chunk of code. Weird if you’re not used to that kind of thing (a.k.a., closures).

“Closures are self-contained blocks of functionality that can be passed around and used in your code.”
iOS Developer Documentation on Closures

Completion handlers are super convenient when your app is doing something that might take a little while, like making an API call, and you need to do something when that task is done, like updating the UI to show the data. You’ll see completion handlers in Apple’s APIs like dataTask(with request: completionHandler:) and they can be pretty handy in your own code.

In dataTask(with request: completionHandler:) the completion handler argument has a signature like this:


completionHandler: (Data?, URLResponse?, Error?) -> Void

The completion handler takes a chunk of code with three arguments: (Data?, URLResponse?, Error?) that returns nothing: Void.

To specify a completion handler we can write the closure inline like this:


let task = session.dataTask(with: urlRequest,
completionHandler: { (data, response, error) in
  // this is where the completion handler code goes
})
task.resume()

The code for the completion handler is the bit between the curly brackets. Notice that the three arguments in the closure (data, response, error) match the arguments in the completion handler declaration: (Data?, URLResponse?, Error?). You can specify the types explicitly when you create your closure but it’s not necessary because the compiler can figure it out:


let task = session.dataTask(with: urlRequest, completionHandler:
  { (data: Data?, response: URLResponse?, error: Error?) in
  // this is where the completion handler code goes
  if let response = response {
    print(response)
  }
  if let error = error {
    print(error)
  }
})
task.resume()

Somewhat confusingly, you can actually drop the completionHandler: bit and just tack the closure on at the end of the function call. This is totally equivalent to the code above and a pretty common syntax you’ll see in Swift code:


let task = session.dataTask(with: urlRequest) { (data, response, error) in
  // this is where the completion handler code goes
  if let response = response {
    print(response)
  }
  if let error = error {
    print(error)
  }
}
task.resume()

We’ll be using that trailing closure syntax in the rest of our code. You can use a trailing closure whenever the last argument for a function is a closure.

If you want to ignore some arguments you can tell the compiler that you don’t want them by replacing them with _ (like we did earlier when we weren’t ready to implement the completion handler yet). For example, if we only need the data and error arguments but not the response in our completion handler:


let task = session.dataTask(with: urlRequest) { (data, _, error) in
  // can't do print(response) since we don't have response
  if let error = error {
    print(error)
  }
}
task.resume()

We can also declare the closure as a variable then pass it in when we call session.dataTask(with:). That’s handy if we want to use the same completion handler for multiple tasks. Here’s how you can store a completion handler in a variable:


let myCompletionHandler: (Data?, URLResponse?, Error?) -> Void = { 
  (data, response, error) in
  // this is where the completion handler code goes
  if let response = response {
    print(response)
  }
  if let error = error {
    print(error)
  }
}
let task = session.dataTask(with: urlRequest, completionHandler: myCompletionHandler)
task.resume()

When Does a Completion Handler Get Called?

So that’s how you specify a completion handler. But when we run that code what’ll happen to our closure? It won’t get called right away when we call dataTask(with request: completionHandler:). That’s good thing, if it were called immediately then we wouldn’t have the results of the web service call yet.

Somewhere in Apple’s implementation of that function it will get called like this:


open func dataTask(with request: URLRequest,
  completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void)
  -> URLSessionDataTask {
  // make an URL request
  // wait for results
  // check for errors and stuff
  completionHandler(data, response, error)
  // return the data task
}

You don’t need to write that in your own code, it’s already implemented in dataTask(with: completionHandler:). In fact, there are probably a few calls like that for handling success and error cases. The completion handler will just sit around waiting to be called whenever dataTask(with: completionHandler:) is done.

Implementing a Useful Completion Handler

So what’s the point of completion handlers? Well, we can use them to take action when something is done. Like here we could set up a completion handler to print out the results and any potential errors so we can make sure our API call worked. Let’s go back to our dataTask(with request: completionHandler:) example and implement a useful completion handler. Here’s where the code will go:


let task = session.dataTask(with: urlRequest) { data, response, error in
  // do stuff with response, data & error here
}
task.resume()

Now we have access to three arguments: the data returned by the request, the URL response, and an error (if one occurred). So let’s check for errors and figure out how to get at the data that we want: the first todo’s title. We need to:

  1. Make sure we got data and no error
  2. Try to transform the data into JSON (since that’s the format returned by the API)
  3. Access the todo object in the JSON and print out the title

let task = session.dataTask(with: urlRequest) {
  (data, response, error) in
  // check for any errors
  guard error == nil else {
    print("error calling GET on /todos/1")
    print(error!)
    return
  }
  // make sure we got data
  guard let responseData = data else {
    print("Error: did not receive data")
    return
  }
  // parse the result as JSON, since that's what the API provides
  do {
    guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])
      as? [String: Any] else {
      print("error trying to convert data to JSON")
      return
    }
    // now we have the todo
    // let's just print it to prove we can access it
    print("The todo is: " + todo.description)
    
    // the todo object is a dictionary
    // so we just access the title using the "title" key
    // so check for a title and print it if we have one
    guard let todoTitle = todo["title"] as? String else {
      print("Could not get todo title from JSON")
      return
    }
    print("The title is: " + todoTitle)
  } catch  {
    print("error trying to convert data to JSON")
    return
  }
}
task.resume()

Which prints out:


The todo is: {
    completed = 0;
    id = 1;
    title = "delectus aut autem";
    userId = 1;
}
The title is: delectus aut autem

And That’s All About Completion Handlers

Hopefully that demystifies completion handlers and code blocks in Swift for you. If you have any questions just leave a comment below and I’ll respond as soon as I can.

If you’d like more Swift tutorials on topics like this one, sign up below to get them sent directly to your inbox.