Understanding Completion Handlers in Swift

Completion handlers can be confusing. In this tutorial you will learn what they are and why you should use them.

Understanding Completion Handlers in Swift

What is a completion handler

A completion handler is basically just a function that gets passed as a parameter of another function.

The reason that we would want to do this is because we want to be notified when something is complete. Generally we use functions that return a value, but this is only useful when we are doing synchronous work. For asynchronous work we use completion handlers.

A good example of this is when we do a network request. Network requests are asynchronous. This means that the code will not wait for the request to return before it runs the rest of the code. So in order to get around this we need to pass a function in and once the request is complete, it will call that function which will perform the required action.

This is an illustration of synchronous code and asynchronous code:

sync vs async

In the above example you can see how with the async example there are multiple things happening at once. The dotted blue represent an unknown length of time. Basically the async task could complete very quickly or very slowly, we do not know how fast it will be done, so to handle this situation we use a completion handler.

Time for an example. We will create a simple class called Completion, this class will have one method called count. This method will count from 0 to 100. When it reaches 15, this is just a random value, it will make a simple network request to https://google.com. Once that request has completed, we will print Received response. The received response will be in the completion handler.

class Completion {
    func count() {
        for i in 0...100 {
            if i == 15 {
                if let url = URL(string: "https://google.com") {
                    URLSession.shared.dataTask(with: url) { (data, response, error) in
                        print("Received response")
                    }.resume()
                }
            }
            
            print("I = ", i)
        }
    }
}

let newInstance = Completion()
newInstance.count()

When you run that code, all the numbers will be printed out in the console, but the Received response will only get printed after all those other numbers were printed.

This is why we use the completion handler. It allows us to perform a task, in this case printing Received response to the console, and be able to handle the output of that task whenever it is completed. In this case the network response happens after all the other numbers have been printed to the console.

Why use a completion handler

We use completion handlers to handle the response of a task. This task will usually be an asynchronous task which means that we have no idea when it will end. The completion handler allows us to handle the response whenever the task is complete.

Conclusion

Completion handlers are super useful. They allow us to run tasks asynchronously and handle the response/result of that task. This means that we do not need to block code that is running if a task will take an unknown amount of time to complete.