What Is a Completion Handler in Swift?


In swift, the completion handler is a block of code that is used to perform and handle the completion state of a task. For example, completion handlers are very commonly used in network requests using URLSession class. Basically, the completion handler is passed as a function argument and called when the task is completed. It does not impact whether the task is completed successfully or not.

You can use the completion handler to perform different actions in your iOS applications. There are some common practices to implement the completion handler such as returning status after cleaning up the unused resources in FileManager operations, updating multiple user interfaces inside a block of statements, and returning after completion, etc.

In this article, you will see the below examples to see how to use completion handlers effectively −

  • Fetch data using the URLSession class and return the completion handler after getting the response from the URL.

  • Filtering an array and returning the completion handler after successfully filtering out the array.

  • Perform a network request for user authentication using email and password. After getting the response from the server, returns the completion handler.

Let's see different examples of completion handlers in Swift −

Fetch data using the URLSession() class

Here's an example of a function that takes a completion handler as an argument −

Example

func fetchData(_ completion: @escaping (_ success: Bool, _ data: Data?) -> Void) {
    
   // Build an URL
   let url = URL(string: "sample_url")!
    
   // Perform the network request
   let task = URLSession.shared.dataTask(with: url) { data, response, error in
      if let data = data {
         // Success, call the completion handler with the data
         completion(true, data)
      } else {
         // Failure, call the completion handler with nil data
         completion(false, nil)
      }
   }
   task.resume()
}

And here's an example of how you might use the above function −

self.fetchData { success, data in
   if success {        
      guard let dataObject = data else { return }
        
      // Parse the data and update the UI
      let json = try? JSONSerialization.jsonObject(with: dataObject)
      print(json!)        
   } else {
      // Show an error message
      print("Network request failed")
   }
}

The fetchData() function in this example accepts a completion handler as an argument. A successful Boolean and the Data delivered by the network call are the two arguments sent to the completion handler. This function makes a network request; when it is successful or unsuccessful, it calls the completion handler and passes it the relevant success and data values.

Filtering an Array

Another example is for operations that take time such as filtering an array

Example

func filterArray(_ array: [Int], completion: @escaping (_ filteredArray: [Int]) -> Void) {
   DispatchQueue.global().async {
      let filteredArray = array.filter { $0 > 5 }
      DispatchQueue.main.async {
         completion(filteredArray)
      }
   }
}

And here's an example of how you might use the above function −

let originalArray: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filterArray(originalArray) { filteredArray in
   print("Original array: \(originalArray)")
   print("Filtered array: \(filteredArray)")
}

Output

Original array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Filtered array: [6, 7, 8, 9, 10]

User Authentication Process

Here's an example of a function that uses a completion handler to handle the result of a user authentication process

Example

func login(username: String, password: String, completion: @escaping (_ success: Bool, _ error: Error?) -> Void) {
   // Perform the login process
   Auth.auth().signIn(withEmail: username, password: password) { authResult, error in
      if let error = error {
         // Login failed, call the completion handler with the error
         completion(false, error)
      } else {
         // Login succeeded, call the completion handler with success
         completion(true, nil)
      }
   }
}

And here's an example of how you might use this function

login(username: "user@example.com", password: "password") { success, error in
   if success {
      // Show the home screen
   } else {
      // Show an error message
      print(error)
   }
}

This function performs the task of signing in the user with the given email and password. The completion handler is called with the result of the login process. if the login process is successful, it calls the completion handler with success otherwise, it passes the error message.

There are points you should remember for using the completion handlers in your code:

  • Sometimes, the completion handlers can make your code complex to read and understand in the case of nested completion handlers.

  • The completion handlers can be difficult to manage for asynchronous operations.

  • In the case of nested completion handlers, callbacks can be difficult to maintain.

  • Incorrect use of completion handlers can create retain cycles which cause memory leaks.

Conclusion

In each illustration, the function carries out a specific task and then calls the completion handler to deal with the outcome. Utilizing completion handlers enables the function to complete the task asynchronously and return right away without delaying the rest of the program's execution.

The completion handler can be used to update the user interface or to carry out any necessary cleanup or processing of the task's results. As a result, the function can return right away rather than having to wait for the work to finish, and the developer can handle the outcome without blocking. Swift programmers frequently employ completion handlers, which is a useful technique for managing asynchronous processes and enhancing the responsiveness of your code.

Updated on: 28-Feb-2023

11K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements