Practical Examples of Using Closures in Swift

Explore diverse examples of using closures in Swift to enhance your programming skills.
By Jamie

Understanding Closures in Swift

Closures are self-contained blocks of functionality that can be passed around and used in your code. They can capture and store references to variables and constants from the context in which they are defined. This feature makes closures a powerful tool in Swift for handling asynchronous tasks, callbacks, and more. Below, we provide three practical examples of using closures in Swift to illustrate their versatility.

1. Closure as a Completion Handler

Context

A common use case for closures in Swift is to handle asynchronous operations, such as networking. In this example, we demonstrate how to define a closure that acts as a completion handler for a data-fetching function.

func fetchData(completion: @escaping (String) -> Void) {
    let url = URL(string: "https://api.example.com/data")!
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data, error == nil else {
            completion("Error fetching data")
            return
        }
        let result = String(data: data, encoding: .utf8) ?? "No data"
        completion(result)
    }
    task.resume()
}

fetchData { result in
    print("Fetched data: \(result)")
}

Notes

  • The @escaping keyword indicates that the closure can outlive the function call, which is essential for asynchronous operations.
  • This example highlights how closures can simplify code by allowing you to define what happens after data is fetched.

2. Sorting an Array with a Closure

Context

Closures can also be used as arguments for functions that require custom sorting logic. In this example, we sort an array of integers in descending order using a closure.

let numbers = [5, 3, 8, 1, 2]
let sortedNumbers = numbers.sorted { (a, b) in
    return a > b
}
print(sortedNumbers) // Output: [8, 5, 3, 2, 1]

Notes

  • The closure { (a, b) in return a > b } defines the sorting criteria, allowing for flexible sorting logic.
  • Swift’s type inference allows us to simplify the closure: let sortedNumbers = numbers.sorted { $0 > $1 }.

3. Capturing Values in Closures

Context

Closures can capture and store references to variables and constants from their surrounding context. This is useful for maintaining state across different calls. In this example, we create a closure that increments a counter.

func makeIncrementer(incrementAmount: Int) -> () -> Int {
    var total = 0
    let incrementer: () -> Int = {
        total += incrementAmount
        return total
    }
    return incrementer
}

let incrementByTwo = makeIncrementer(incrementAmount: 2)
print(incrementByTwo()) // Output: 2
print(incrementByTwo()) // Output: 4

Notes

  • The makeIncrementer function returns a closure that captures the total variable and the incrementAmount parameter.
  • Each time the incrementByTwo closure is called, it maintains its state, demonstrating the power of closures in Swift.

These examples of using closures in Swift illustrate their flexibility and practicality in various programming scenarios, enhancing your ability to write clean and efficient code.