Monday, January 1, 2018

Swift Escaping Closures (@escaping)

I am not going to explain what closures are and the purpose of them in Swift language in this post.

Ever got the below compile error message when you tried to save a closure in a global variable for future use?



Let’s see what is this @escaping and non-escaping closures.

From Swift 3, closures are non-escaping by default. That means they should be used with in the function body and on return.



    override func viewDidLoad() {
        super.viewDidLoad()
        getUserName(userID: 12) { (name: String) in
            print("\(name)")
        }
    }
    
    func getUserName(userID: Int, 
                    completion: (_ name: String) -> Void) {
        completion("Karunakar Bandikatla")
    }



I am using a simple closure here which returns the user name. In getUserName function, I am calling the completion, So the closure is not escaping.

Let’s see If we need to make a local data storage call to get user name.



 class LocalData {

    class func getUserName(userID: Int, 
                      completion: (_ name: String) -> Void) {

        completion("Karunakar Bandikatla")

    }

 }

 func getUserName(userID: Int, 
                  completion: (_ name: String) -> Void) {

    LocalData.getUserName(userID: userID, 
                                                  completion: completion)


 }



In this case also, I am using the closure in function body by passing it as an argument to LocalData class method getUserName. Here also the closure is not escaping and we are good.

In both the above cases, There is a certainty that when and where I am going to use the closure. So they could be of default behaviour, which is non-escaping.

Let’s see where it’s necessary and must to go for an @escaping closure and how compiler helps you in using @escaping for closures in such scenarios by giving build errors.

#1

I want to save the closure as a global variable so that I can use it later after doing a wholesome of operations that need to be executed. 





   func getUserName(userID: Int, 

                     completion: (_ name: String) -> Void) {

        completionHandler = completion
    }


We can not. And as per the error, It should be a escaping closure If we need to store that in a global variable.




    func getUserName(userID: Int, 

       completion: @escaping (_ name: String) -> Void) {

        completionHandler = completion
    }



So, Storage in a global variable should be a @escaping closure parameter.

#2

I need to do an Asynchronous operation(Calling server for data) and after that I can use the closure.



  class ServerData {
     class func getUserName(userID: Int, 
                      completion: (_ name: String) -> Void) {

         DispatchQueue.global(qos: .background).async {
            //Service call
             DispatchQueue.main.async {
                completion("Karunakar Bandikatla")
             }
         }
      }
   }

    func getUserName(userID: Int, 
       completion: @escaping (_ name: String) -> Void) {

        ServerData.getUserName(userID: userID, 
                                                 completion: completion)
    }





It’s clearly saying that, the parameter must be a escaping closure to use that in asynchronous operations.



  class ServerData {
    class func getUserName(userID: Int, 
         completion: @escaping (_ name: String) -> Void) {

        DispatchQueue.global(qos: .background).async {
            //Service call
            DispatchQueue.main.async {
                completion("Karunakar Bandikatla")
            }
        }
    }
  }



Conclusion:

Closures should be escaping In below scenarios where the default non-escaping behaviour doesn’t work.

  1. Storing closures as global variables
  2. Asynchronous Operations


Hope this post is useful. Feel free to comment incase of any queries.