Swift Completion Handler FAQs
July 27, 2017 - Swift 3

Completion handlers can be a bit tough to wrap your head around when you first encounter them. Here are a few of the most frequently asked questions about completion handlers that I see from newer Swift developers.

Ask More Questions
(image by Jonathan Simcoe)
  1. What is a completion handler?
  2. How do I get data out of a function with a completion handler? Why can’t I just return it?
  3. What else could I use instead?
  4. Do I have to use (data, response, error) -> Void in my completion handler? Can I name those elements differently? What’s valid when I’m providing that argument? What are some different examples of what it might look like?
  5. What’s all this weak self stuff I keep hearing about?
  6. Why can’t I do X in my completion handler? Why does my app crash when I do X in my completion handler?
  7. Why can’t I step into the completion handler when I’m debugging?
  8. Why do I sometimes need to add @escaping?

What is a completion handler?

If you want the long version, I wrote a whole post about completion handlers.

TL;DR: A completion handler is a closure (“a self-contained block of functionality that can be passed around and used in your code”). It gets passed to a function as an argument and then called when that function is done.

The point of a completion handler is to tell whatever is calling that function that it’s done and optionally to give it some data or an error. Sometimes they’re called callbacks since they call back to whatever called the function they’re in.

Here’s an example of a function that takes a completion handler:


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
}

It takes two arguments: request: URLRequest and completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void.

The second argument is a closure that takes (Data?, URLResponse?, Error?) as arguments and returns nothing (Swift.Void). Completion handlers nearly always return Void.

Here’s how you’d call it and pass in those two arguments:


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

If a closure is the last argument, we can use trailing closure syntax and just write the closure after the rest of the function call:


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

When we run that code dataTask(with: urlRequest) will run until it has a result or error to pass back to the caller. Then it’ll call the completion handler like completionHandler(data, response, error). Then the code that we wrote in the completion handler will get called with those arguments.

How do I get data out of a function with a completion handler? Why can’t I just return it?

Often completion handlers are used for asynchronous functions. We use async functions when we want do to something but don’t want to hold up the app waiting for the result. For example, when we make a network call to fetch some data, the UI of the app shouldn’t freeze up while we’re waiting for the response.

By using a completion handler, we can fire off the network call and not wait around for the response. When the network call is done, the completion handler can be called to notify us. So we can keep the UI working while the networking call is being made.

If a function is asynchronous then we can’t just immediately return the result because we don’t have it when the function first runs. We’ll get the result later after the asynchronous operation is done.

Let’s look at a simple case where we just want to know if a networking call succeeds or fails (assuming we’ll get an error for all the possible failure cases, which might not be the case with all APIs). Once we get the result, let’s assume we want to save some data in a variable then reload a table view. Our initial attempt might look like this:


func reloadTableViewAfterNetworkCall() {
  self.hasData = doesCallSucceed()
  self.tableView.reloadData()
}

func doesCallSucceed() -> Bool {
  let task = session.dataTask(with: urlRequest) {
    (data, response, error) in
  }
  task.resume()
  return error == nil
}

But that won’t compile. error is part of what we get once the network call is done. The error variable only exists within the completion handler code block.

task.resume() fires off the network call but when we try to call return error == nil right after that the network call won’t have completed yet. We have to wait until we have a result from the network call to set self.hasData. So we need to do the reloading in the dataTask completion handler:


func reloadTableViewAfterNetworkCall() {
  fetchDoesCallSucceed()
}

func fetchDoesCallSucceed() {
  let task = session.dataTask(with: urlRequest) {
    (data, response, error) in
    let success = error == nil
    self.hasData = success
    self.tableView.reloadData()
  }
  task.resume()
}

Now we’re setting our hasData variable and reloading the table view after the network call is done. Notice that fetchDoesCallSucceed() doesn’t have a return type declared.

This code will work but it means both of those functions need to be in the same class (since we’re using self inside of the dataTask completion handler). If we want to move the fetchDoesCallSucceed() function into another class so we’re not creating a massive view controller, we can use another completion handler:


func reloadTableViewAfterNetworkCall() {
  fetchDoesCallSucceed() {
    success in
    self.hasData = success
    self.tableView.reloadData()
  }
}

func fetchDoesCallSucceed(completionHandler: @escaping (Bool) -> Void) {
  let task = session.dataTask(with: urlRequest) {
    (data, response, error) in
    let success = (error == nil)
    completionHandler(success)
  }
  task.resume()
}

fetchDoesCallSucceed now takes a completion handler with a single argument. So when we call it we pass in a closure with a single argument like { success in ... }. And when the network call is done, we call that completion handler like completionHandler(success). Now we could move fetchDoesCallSucceed into another class. You can do the same thing to move the code in the completion handler into its own function in the same class to keep the function lengths reasonable.

It’s pretty common to end up with a few layers of completion handlers when dealing with network calls. Often you’ll do something similar to what we’re doing here: turn the (data, response, error) in the completion handler of a dataTask (or the response in an Alamofire response handler) into the data that you really want. Then you’ll pass that data to another completion handler. Often that means parsing JSON into objects and returning them:


func reloadTableViewAfterNetworkCall() {
  fetchItemsToDisplay() {
    (items, error) in
    if let error = error {
      self.items = nil
      self.handleError(error)
      return
    }
    
    self.items = items
    self.tableView.reloadData()
  }
}

func fetchItemsToDisplay(completionHandler: @escaping ([Item]?, Error?) -> Void) {
  let task = session.dataTask(with: urlRequest) {
    (data, response, error) in
    if let error = error {
      completionHandler(nil, error)
      return
    }

    let parsedItems = self.parseResponseData(data)
    completionHandler(items, nil)
  }
  task.resume()
}

Here the two completion handlers have different types since we’re converting the network call results to [Item] if we can. The inner completion handler is (Data?, URLResponse?, Error?) -> Void and the outer one is ([Item]?, Error?) -> Void.

What else could I use instead?

There are a few ways other to work with asynchronous calls. The classic way in iOS was to use Delegates and Protocols. There are a few more hotter approaches to handling async functions that are becoming more common in Swift though they have a bit of a learning curve: Futures / Promises and Functional Reactive Programming.

Delegates & Protocols

You could assign a delegate and call a function on it when the operation is done:


class MyViewController {
  func reloadTableViewAfterNetworkCall() {
    let networkController = NetworkController()
    networkController.delegate = self
    networkController.fetchDoesCallSucceed()

    func didGetResult(_ success: Bool) {
      self.hasData = success
      self.tableView.reloadData()
    }
  }
}

class NetworkController {
  var delegate: MyViewController?
  func fetchDoesCallSucceed() {
    let task = session.dataTask(with: urlRequest) {
      (data, response, error) in
      let success = error == nil
      delegate?.didGetResult(success)
    }
    task.resume()
  }
}

If there are a few different classes that could be the delegate, use a protocol to define the functions that they must have:


protocol NetworkControllerDelegate {
  func didGetResult(_ success: Bool)
}

class MyViewController: NetworkControllerDelegate {
  func reloadTableViewAfterNetworkCall() {
    let networkController = NetworkController()
    networkController.delegate = self
    networkController.fetchDoesCallSucceed()

    func didGetResult(_ success: Bool) {
      self.hasData = success
      self.tableView.reloadData()
    }
  }
}

class NetworkController {
  var delegate: NetworkControllerDelegate?
  func fetchDoesCallSucceed() {
    let task = session.dataTask(with: urlRequest) {
      (data, response, error) in
      let success = error == nil
      delegate?.didGetResult(success)
    }
    task.resume()
  }
}

You’ll see delegates and protocols in a lot of Apple’s APIs though closures are becoming more common.

Futures / Promises

A Promise or Future is something that will eventually have a value. They’re one way of handling async operations functionally. Swift and iOS don’t include a default implementation of Promises or Futures.

Third-party libraries like Bright Futures or PromiseKit can be used to write Swift with Promises and Futures:


func fetchItems() -> Future<[Item]> {
  return Future { completion in
    let task = session.dataTask(with: urlRequest) {
    (data, response, error) in
    if let error = error {
      completion(.failure(error))
      return
    }

    let parsedItems = self.parseResponseData(data)
    completion(.success(items))
  }
  task.resume()
  }
}

func reloadTableViewAfterNetworkCall() {
  fetchItemsToDisplay()
  .then { posts in
    self.items = items
    self.tableView.reloadData()
  }.catch { error in
    self.items = nil
    self.handleError(error)
  }
}

In this case, we’re returning a future: Future<[Item]> from fetchItems(). That future is an object that will eventually have a value of type [Item]. When the Future is created we define it using a closure that eventually calls a completion handler:


Future { completion in
  // do stuff...
  // eventually finish and provide the value or return an error
  if success {
    completion(.success(items))
  } else {
    completion(.failure(error))
  }
}

A really nice feature is that you can chain Futures and use them to flatter out layers of callbacks. E.g., if you had another function that you needed to run after fetching the items:


func reloadTableViewAfterNetworkCall() {
  fetchItemsToDisplay()
  .then { items in
    processItems(items)
  }
  .then { processedItems in
    self.items = processedItems
    self.tableView.reloadData()
  }.catch { error in
    self.items = nil
    self.handleError(error)
  }
}

processItems(items) might even make another API call or do another async operation.

Functional Reactive Programming

You can also handle async calls using Functional Reactive Programming. In that case, you’ll hook up observers that will react to changes to properties. FRP is a pretty big topic but here’s a decent intro tutorial that’ll show you how to use it with a search bar:


searchBar
    .rx.text // Observable property thanks to RxCocoa
    .orEmpty // Make it non-optional
    .subscribe(onNext: { [unowned self] query in // Here we will be notified of every new value
        self.shownCities = self.allCities.filter { $0.hasPrefix(query) } // We now do our "API Request" to find cities.
        self.tableView.reloadData() // And reload table view data.
    })
    .addDisposableTo(disposeBag)

If you’re interested in going that route, here’s a more comprehensive guide on Functional Reactive Programming

Do I have to use (data, response, error) -> Void in my completion handler? Can I name those elements differently? What’s valid when I’m providing that argument? What are some different examples of what it might look like?

Like we mentioned earlier if the last parameter in a function is a closure Swift lets you drop the label for that parameter and just put a closure after the function call. It’s called a trailing closure. So this:


let task = session.dataTask(with: request) {
    (data, response, error) -> Void In
    // ...
}

Is a short form for this:


let task = session.dataTask(with: request, completionHandler: {
    (data, response, error) -> Void In
    // ...
})

Which makes it a little more clear that you’re passing a closure as a parameter to the function.


(data, response, error) -> Void

Is the type declaration for that closure: it takes in (data, response, error) and returns Void (i.e., nothing).

If you check the definition for that function, you’ll see that it matches the types declared there:


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

So when you use session.dataTask(with: completionHandler:) the parameters in that closure that you pass for the completion handler have to match:


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

You can match those types explicitly:


(data: Data?, response: URLResponse?, error: Error?) -> Void in

Or let the compiler figure out the types:


(data, response, error) -> Void in

And omit Void since it can be inferred:


(data, response, error) in

You can name the parameters whatever you want as long, as you have the right number and types if you provide them. So this would also work:


(data, stuff, error) in

But this wouldn’t work since the types don’t match:


(data, items: [Item]?, error) -> Void in

You can’t change the number of parameters, so neither of these would work:


(data, response, error, anotherParameter) in
(data, response) in

But if you’re not using a parameter, you can use _ as a placeholder:


(_, _, theOneParameterICareAbout) in

What’s all this weak self stuff I keep hearing about?

weak self is a technique used in closures to avoid retain cycles. Since a closure is an object it can own or retain other objects. This ownership can create a problem if an object that owns the closure is also owned by the closure. If that happens then we won’t ever deallocate that object or the closure because they’ll always be owned by something. The most common case is for the closure to end up owning the class that it’s declared in. For example, if the closure is kept around as a variable and it refers to self then we’ll have a retain cycle.

Here’s an example of how that could happen:


class MyViewController: ViewController {
  let fetchCallCompletionHandler: ((Bool) -> Void) = {
    success in
    self.doStuff()
  }

  func reloadTableViewAfterNetworkCall() {
    fetchDoesCallSucceed(completionHandler: fetchCallCompletionHandler)
  }

  func fetchDoesCallSucceed(completionHandler: @escaping (Bool) -> Void) {
    let urlRequest = URLRequest(url: URL(string: "https://google.com")!)
    let session = URLSession.shared
    let task = session.dataTask(with: urlRequest) {
      (data, response, error) in
      let success = (error == nil)
      completionHandler(success)
    }
    task.resume()
  }

  func doStuff() {
  }
}

You might do something like that if you wanted to use that completion handler after different network calls.

fetchCallCompletionHandler is owned by MyViewController. Normally it would be retained by MyViewController and then deallocated when MyViewController is freed. But since it uses self it also retains MyViewController.

To avoid this retain cycle we can make the closure not own MyViewController:


class MyViewController {
  let fetchCallCompletionHandler: ((Bool) -> Void) = { 
    [weak self] success in
    // ...
  }
}

But that property will get initialized before self is completely set up so the compiler will complain about using self then. We can fix it by making it a lazy var so it’s not set up until we actually use it:


class MyViewController {
  lazy var fetchCallCompletionHandler: ((Bool) -> Void) = { 
    [weak self] success in
    // ...
  }
}

You won’t need to do make anything lazy if you’re not storing the closure in a variable but you can still use weak self in those cases:


class MyViewController: ViewController {
  func reloadTableViewAfterNetworkCall() {
    fetchDoesCallSucceed(completionHandler: {
      [weak self] success in
      self?.doStuff()
    })
  }

  // ...
}

[weak self] is a capture list: it lists the items that the closure captures. Without it the closure would implicitly capture self strongly.

If we try to compile that the compiler complain because [weak self] has made self optional in that block. Weak references in Swift are always optional. So we need to treat self like it’s optional. How we want to do that depends on what should happen if self is nil when the closure is executed. In this case, we’re updating the data and reloading the table view. It doesn’t make sense to do that if the view controller now longer exists, so we’ll just return if self is nil:


lazy var fetchCallCompletionHandler: ((Bool) -> Void) = { [weak self]
  success in
  guard let strongSelf = self else {
    return
  }

  strongSelf.hasData = success
  strongSelf.tableView.reloadData()
}

Other times it might make sense to perform some of the actions even though self is nil:


lazy var fetchCaĆ’llCompletionHandler = {
  [weak self] success in
  self?.hasData = success
  self?.tableView.reloadData()
}

Why can’t I do X in my completion handler? Why does my app crash when I do X in my completion handler?

Sometimes when you try to do UI updates in a completion handler you’ll get a crash like:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-... may only be called from the main thread.'

The error message is telling you what the problem is: the function you want to call can only be done on the main thread. Sometimes completion handlers are working on background threads. If you’re working with an Apple API or a third party one then check the documentation to see if the call might be happening on a background thread. For example, the Alamofire documentation says this:

Response handlers by default are executed on the main dispatch queue. However, a custom dispatch queue can be provided instead.

So usually you can call something like self.tableview.reloadData() from an Alamofire response handler. But if you use a custom queue then you can’t. Other APIs may always call your completion handlers on background threads or not specifically use the main thread.

To handle this issue, make those calls on the main thread by wrapping them in a call to DispatchQueue.main.async:


func reloadTableViewAfterNetworkCall() {
  fetchItemsToDisplay() {
    (items, error) in
    if let error = error {
      self.items = nil
      self.handleError(error)
      return
    }
    
    self.items = items
    DispatchQueue.main.async {
      self.tableView.reloadData()
    }
  }
}

The code that is wrapped in DispatchQueue.main.async { ... } will run on the main thread.

Why can’t I step into the completion handler when I’m debugging?

Because it’s async! Just like how we couldn’t return with the result immediately after making a network call, we can’t step from the line that makes the call to the first line that handles the response. Those two lines of code aren’t run one right after the other.

To step through code in a completion handler put a breakpoint on the first line of code inside of the completion handler:

Then the debugger will stop there when the completion handler gets called and you can step through the rest of the closure.

Why do I sometimes need to add @escaping?

If the a closure passed to a function gets called after the function that it’s passed to then you have an escaping closure. That’s pretty frequently the case with async calls. For example, if you make a network call then use the completion handler after the call is completed then the closure is probably getting run after the original function finished:


func fetchDoesCallSucceed(completionHandler: @escaping (Bool) -> Void) {
  let task = session.dataTask(with: urlRequest) {
    (data, response, error) in
    let success = (error == nil)
    completionHandler(success)
  }
  task.resume()
}

In this case, here’s what will happen:

  1. fetchDoesCallSucceed is called
  2. session.dataTask(with: urlRequest) is called
  3. fetchDoesCallSucceed finishes running
  4. The network call finishes or times out
  5. completionHandler(success) gets called

Since completionHandler(success) happens after fetchDoesCallSucceed finishes running, the closure escaped. It can also happen if the closure is stored as a variable like we did earlier with lazy var fetchCallCompletionHandler = { ... }. In those cases, tell the compiler that the closure can escape by including @escaping.

The compiler will tell you if you miss any @escaping labels when declaring functions that take closures. Now you know why and when you need to add it.

And That’s the FAQs About Completion Handlers

Hopefully that answers some of your questions about completion handlers and closures in Swift.

If you have any more questions about completion handlers in Swift just leave a comment below and I’ll respond as soon as I can.

Want more Swift tutorials like this one?

Sign up to get the latest GrokSwift tutorials sent straight to your inbox

iOS Apps with REST APIs Cover

With iOS Apps with REST APIs ebook I’ll teach you how to build Swift apps that get their data from web services without hundreds of pages about flatmap and Core Whatever. iOS Apps with REST APIs ebook is the book I wish I had on my first iOS contract when I was struggling to figure out how to get the API data showing up in the app I was building.

Other Posts You Might Like