Completion Handler in Swift

When you are new to Swift, and you see completion handlers, these are the most mysterious guys around. You might have found it difficult to get grasp on it.
Don’t worry we will demystify them in this article.
I will explain to you what are they, why are they there and how they operate.
After reading this blog post you will be confident to deal with completion handlers, will fall in love with them and start making your own.
So lets get started

Completion handlers are nothing but block of code!!
YES! They are block of code which you tell to execute when something has completed. Its like scheduling some commands/statements to be executed in future when some event has completed.

For example you are watching a movie, suddenly you feel hungry. What you do is, you order pizza. Obviously you are not going to get it immediately. And if you are not dumb you will not stop watching movie while waiting for pizza to arrive. You order pizza and resume your movie. So you have scheduled the pizza and resumed your movie, but when the bell rings(This is our completion handler) you pause the movie collect the pizza and start finishing it.While eating you obviously keep watching movie.(Probably you are octa core).

Now lets implement this.

I am creating a single view application to do this.
We will first create separate queue for Pizza ordering and processing.
Use the following line to create a queue, outside any function

let pizzaProcessingQue = DispatchQueue(label: "com.swiftwithsadiq.pizzaque")

Now lets define our own orderPizza function that takes a completion handler

    func orderPizza(completionHandler : @escaping () -> Void) {
        pizzaProcessingQue.async {
            print("Preparing pizza")
            sleep(5)
            print("Ting tong!!! Your order is at your door step")
            completionHandler()
        }
        print("Order placed successfully!!")
    }

The above function takes a completion handler. We have mentioned that our completion handler is going to accept no arguments(notice the empty braces) and going to return nothing(notice Void).
So when you call orderPizza,mention what you want to do when the pizza arrives along with this call. This can be done very easily by passing a block of code that accepts nothing and returns nothing.(This is what completionHandler for orderPizza expects). So lets do that

        orderPizza {
          
                print("Received pizza")
                print("Earting pizza and watching movie")
        }

Note the way we are passing the completion handler. We have defined our completion handler in curly braces. This is the way Swift allows you to pass the closures if they are the last argument of the function. (orderPizza has just one argument i.e. our completionHandler, so it is the first and last)

I will be adding two print statement just before and after calling orderPizza to make things clearer. I am putting these in viewDidLoad of viewController. My view controller looks like this after all of this.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        print("Watching movie")
        
        orderPizza {
          
                print("Received pizza")
                print("Eating pizza and watching movie")
        }
        
        print("Watching movie after ordering pizza")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    let pizzaProcessingQue = DispatchQueue(label: "com.swiftwithsadiq.pizzaque")
    
    func orderPizza(completionHandler : @escaping () -> Void) {
        pizzaProcessingQue.async {
            print("Preparing pizza")
            sleep(5)
            print("Ting tong!!! Your order is at your door step")
            completionHandler()
        }
        print("Order placed successfully!!")
    }
}

Now run the program, hold your breath and wait for output

Screen Shot 2017-07-20 at 11.29.00 PM
Note the sequence of operations.
We start with watching movie.
We order pizza. Pizza starts preparing.
Meanwhile we resume our movie.
Once the pizza is ready (after 5 seconds sleep) orderPizza calls back the block of code i.e. closure that we sent it.(This closure has been stored in completionHanlder). So calling completionHanlder actually brings the execution back to our following two lines.

{
      print("Received pizza")
      print("Eating pizza and watching movie")
}

I hope you have understood what completion handlers are, why we use them and how they operate.
Completion handles can take some arguments as well. Lets add one Bool argument ‘success’ to our completion handler.

    func orderPizza(completionHandler : @escaping (_ success : Bool) -> Void) {
        pizzaProcessingQue.async {
            print("Preparing pizza")
            sleep(5)
            print("Ting tong!!! Your order is at your door step")
            let randomNumber = arc4random() % 2
            if(randomNumber == 0){
             completionHandler(true)
            } else {
                completionHandler(false)
            }
        }
        print("Order placed successfully!!")
    } 

To keep things simple we have generated random integer. If its even we are assuming that pizza preparation was successful otherwise we failed in delivering pizza. But as a responsible service provider we are intimating our customer in both situations via completion handler.

Now when we call orderPizza we need to handle the situation, where we might not get the pizza. The ViewController code looks like this after all of this.


import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        print("Watching movie")
        
        orderPizza { wasItSuccessfull in
            if(wasItSuccessfull) {
                print("Received pizza")
                print("Eating pizza and watching movie")
            } else {
                print("Oh no!!!. Eating bread!!! Watching movie")
            }
        }
        
        print("Watching movie after ordering pizza")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    let pizzaProcessingQue = DispatchQueue(label: "com.swiftwithsadiq.pizzaque")
    
    func orderPizza(completionHandler : @escaping (_ success : Bool) -> Void) {
        pizzaProcessingQue.async {
            print("Preparing pizza")
            sleep(5)
            print("Ting tong!!! Your order is at your door step")
            let randomNumber = arc4random() % 2
            if(randomNumber == 0){
             completionHandler(true)
            } else {
                completionHandler(false)
            }
        }
        print("Order placed successfully!!")
    }
}

Note that now our completionHandler now consists of following

{ wasItSuccessfull in
            if(wasItSuccessfull) {
                print("Received pizza")
                print("Eating pizza and watching movie")
            } else {
                print("Oh no!!!. Eating bread!!! Watching movie")
            }
        }

I have given the argument name as wasItSuccessful to avoid any confusion that the name of the argument in function which accepts completionHandler (success in our case) and the implementation of completion handler should be same. They can be different!!!

Run the program few times and your order will randomly succeed or fail. And you will get pizza or have to survive upon bread depending upon your luck.

So that was completion handler for you. In app scenario you many time face situation where you can not keep waiting for some operation to complete and want to move on(Most of the times getting data from server).
Completion handler comes handy in such situations.

If you are calling some function with completion handler it might in turn call the completion handler on some queue other than main. So if you are updating some UI in completion handler, its always good to get hold of main queue explicitly to do so.

Before closing, I know you must have noticed @escaping keyword before completion handler. This is required for any closure that can be called after the function which is accepting it has returned. This is almost always the case with completion handlers(You see print(“Order placed successfully!!”) executed before completionHandler).

I hope you have understood the completion handler in Swift.
HAPPY LEARNING!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s