Swift Closures: How Do They Actually Work?

Swift closure is a special type of function. Similar to regular functions, closures accept parameters, execute statements, and return values.

According to Apple:

Closures are self-contained blocks of functionality that can be passed around and used in your code

In Swift, you can use closures as

  • Higher-order functions, such as when sorting or filtering a list.
  • Completion handlers to call back after a lengthy task has finished.

Swift Closures

Let’s start by writing a closure to demonstrate how it works:

let greet = {
    print("Hello!")
}

greet()

Output:

"Hello!"

Let’s break it down:

  • We create a constant greet in which we assign a closure. The closure is all the code after the equals sign.
  • We call greet in a similar fashion as if it was a function.

Now, let’s slightly modify the implementation of greet. We are going to add a name parameter so that we can greet a person with their name:

let greet: (String) -> () = { name in
    print("Hello, \(name)!")
}

greet("Nick")

Output:

"Hello, Nick!"

Let’s inspect this closure:

  • The closure accepts a parameter. Thus, its type needs to be specified. In this case, it takes a name string and returns nothing, thus the type (String) -> ().
  • There is a name parameter within the closure. This is the person’s name we input to the closure when we greet someone.

Passing A Closure to a Function as a Parameter

Let’s create a closure that compares two integers by checking if the second is greater than the first:

let compare: (Int, Int) -> Bool = {
    (n1, n2) in 
    return n1 < n2
}

compare(1,4) // -> true

Next, let’s sort an array of integers with the help of this closure. This happens by passing it into a sorting function.

First, let’s create an array of integers to sort:

let nums = [10, 2, 3, 54, 3, 2, 1030]

In Swift, an array has a sorting method called sort(by:). The by: parameter is a closure. This closure acts as a sorting function. In this example, it compares two integers and sorts the array based on the result of the comparisons.

Let’s pass our compare closure into the sort(by:) function to sort the list from smallest to largest:

let sortedNums = nums.sorted(by: compare)

print(sortedNums) // prints sorted list [2, 2, 3, 3, 10, 54, 1030]

Now we know what closures are and how they work + how they can be passed to a function to for example sort a list of integers.

Next, we are going to take a look at completion handlers that are closures in action.

Completion Handlers—Closures In Action

A common use case for closures in Swift is completion handlers.

Say you perform a lengthy process, such as an HTTP request. You want to do something immediately after the request completes. But you do not want to waste resources in checking if the process is still going on.

This is when completion handlers can help. A completion handler is a closure that calls back as soon as the process completes.

As an example, when making a networking request to a web page, the response may contain data, an URL response, and an error.

Let’s create a closure that handles this kind of response and prints the HTML data of the requested page:

let handler: (Data?, URLResponse?, Error?) -> () = { (data, response, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}

The closure above accepts three optional parameters, data, a response, and an error, and returns nothing.

Let’s now create an URL object for StackOverflow’s main page:

let url = URL(string: "http://www.stackoverflow.com")!

Next, we create a network task to request the StackOverflow page’s contents. The dataTask method takes in a target URL and a completion handler, that is, a closure that is executed when the network request completes.

This completion handler needs to be a closure that is of type (Data?, URLResponse?, Error?) -> () as described above. This is exactly what we created, so let’s pass it into the dataTask method:

let task = URLSession.shared.dataTask(with: url, completionHandler: handler)

task.resume()

As a result, the StackOverflow page’s HTML content is printed in the console.

Now you know how completion handlers utilize closures to call you back after a lengthy process completes.

One More Thing About Writing Closures

You do not need to declare closures separately as I’ve done in the above examples.

To illustrate this, let’s rewrite the network request example so that we give the closure directly into the dataTask function without declaring it separately:

let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    guard let data = data else { return }
    print(String(data: data, encoding: .utf8)!)
}

This is the more common way to pass a closure into a function in Swift.

Conclusion

A closure is a block of code assigned to a variable. This variable can be used like other variables to for example pass it into a function.

The common use case for closures in Swift is completion handlers. A completion handler is a block of code executed after a lengthy action gets completed. This is handy as you do not need to worry about checking if the action is completed or not.

Thanks for reading. Happy coding!

Scroll to Top