Swift optional escaping closure parameter


In Swift, an optional escaping closure parameter is a closure that can be executed after the function it is passed to has returned. In this article, we will see how to create escaping closures as parameters with examples.

To declare an optional escaping closure parameter, you add the @escaping keyword before the closure type to the function's parameter list.

Syntax

Here is the syntax.

func doSomething(completion: @escaping () -> Void) {
   // Write code here
}

In the above code, completion is an optional escaping closure parameter that takes no parameters and returns Void.

The @escaping keyword indicates that the closure may be executed after the doSomething function has returned, which means that the closure is stored for future execution. This is useful in scenarios where the closure is used for asynchronous operations, such as networking requests or animations.

Note that if you pass a closure to a function with an optional escaping closure parameter, you need to explicitly indicate that the closure should be allowed to escape by using the self. prefix when referring to properties or methods inside the closure. This is because the closure may be executed after the function has returned, and thus the closure needs to capture a strong reference to self to ensure that the referenced properties or methods remain valid.

Here's an example that demonstrates how optional escaping closures can be used in Swift

Step 1 − In this step, the NetworkManager class has a function called fetchDataFromServer that accepts an optional escaping closure parameter called completionHandler. This closure takes an array of strings as its parameter and returns Void.

class NetworkManager {    
   func fetchDataFromServer(completionHandler: @escaping ([String]) -> Void) {
      DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2) {
         let data = ["John", "Sarah", "Jane"]
         completionHandler(data)
      }
   }
}

Inside fetchDataFromServer, the closure is stored for later execution and is executed asynchronously after a delay of 2 seconds. The data is then passed to the closure as a parameter.

Step 2 − In TestController, the dataArray property is updated with the data received from the closure, and then the UI is updated on the main thread with the new data.

Note that we use the [weak self] capture list to avoid a strong reference cycle between TestController and the closure. This is because the closure is executed asynchronously, and if TestController is deallocated before the closure is executed, the closure would still hold a strong reference to TestController and prevent it from being deallocated.

Example

class TestController: UIViewController {
    
   private lazy var clickButton: UIButton = {
      let button = UIButton()
      button.addTarget(self, action: #selector(handleButtonClick), for: .touchUpInside)
      button.backgroundColor = .darkGray
      button.setTitle("Fetch Data", for: .normal)
      button.setTitleColor(.white, for: .normal)
      button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
      button.layer.cornerRadius = 5
      button.translatesAutoresizingMaskIntoConstraints = false
      return button
   }()
    
   var networkManager = NetworkManager()
   var dataArray: [String] = []
    
   override func viewDidLoad() {
      super.viewDidLoad()
      initialSetup()
   }
    
   private func initialSetup() {        
      view.backgroundColor = .white
      navigationItem.title = "Escaping Closure"
        
      view.addSubview(clickButton)
        
      clickButton.widthAnchor.constraint(equalToConstant: 200).isActive = true
      clickButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
      clickButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
      clickButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
   }    
   @objc private func handleButtonClick() {   
   }
}

Output

Step 3 − Complete the handleButtonClick() implementation by calling the fetchDataFromServer() method. Here is the code −

Example

@objc private func handleButtonClick() {
   networkManager.fetchDataFromServer { [weak self] data in
      self?.dataArray = data
      DispatchQueue.main.async {
         print("Data fetched: \(data)")
         self?.clickButton.setTitle("Data Fetched", for: .normal)
      }
   }
}

Output

Data fetched: ["John", "Sarah", "Jane"]

In the above example, we define a NetworkManager class with a function called fetchDataFromServer that takes an optional escaping closure parameter called completionHandler. Inside fetchDataFromServer, we simulate a delay of 2 seconds using DispatchQueue.global.asyncAfter and then pass some data (an array of strings) to the closure as a parameter.

When the program runs, we wait for 2 seconds before executing the closure passed to fetchDataFromServer. This closure updates the dataArray property with the received data and then updates the UI.

Another Example

This example shows how optional escaping closures can be used in Swift −

class MyData {
    var data = [1, 2, 3, 4, 5]
    
    func fetchData(completion: @escaping ([Int]) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            completion(self.data)
        }
    }
}
let myData = MyData()
myData.fetchData { (data) in
    print("Data received: \(data)")
}
print("Waiting for data...")

Output

Waiting for data...
Data received: [1, 2, 3, 4, 5]

In the above code, we define a MyData class with a function called fetchData that takes an optional escaping closure parameter called completion. Inside fetchData, we simulate a delay of 1 second using DispatchQueue.main.asyncAfter and then pass the data array to the closure as a parameter.

When the program runs, it first prints "Waiting for data..." and then waits for 1 second before executing the closure passed to fetchData. This closure prints "Data received: [1, 2, 3, 4, 5]" to the console.

Conclusion

In conclusion, optional escaping closures are a powerful feature of Swift that allow us to handle asynchronous code in a concise and efficient way. With optional escaping closures, we can pass functions as parameters to other functions and execute them later, even after the calling function has returned. This makes it easy to write asynchronous code that is both readable and maintainable.

Optional escaping closures can be used in a variety of scenarios, such as network requests, animations, and user interface updates. By using optional escaping closures, we can write code that is both performant and responsive, without sacrificing readability or maintainability.

Updated on: 04-May-2023

896 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements