Swift Docs - Closures

Understanding Closures in Swift

Closures are self-contained blocks of functionality that can be passed around and used in your code. In Swift, closures can capture and store references to variables and constants from the context in which they are defined. This capability is known as closing over those variables.

What is a Closure?

A closure in Swift is similar to a block in C and Objective-C, and to lambdas in other programming languages. Closures come in three forms:

  1. Global functions are closures that have a name and do not capture any values.
  2. Nested functions are closures that have a name and can capture values from their enclosing function.
  3. Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.

Basic Closure Syntax

Let’s start with a simple closure:

let simpleClosure = { 
    print("This is a simple closure") 
}
simpleClosure() // Output: This is a simple closure

Closures with Parameters and Return Values

Closures can accept parameters and return values:

let add: (Int, Int) -> Int = { (a, b) in
    return a + b
}
print(add(5, 3)) // Output: 8

Shorthand Argument Names

Swift provides shorthand syntax for closure arguments:

let multiply: (Int, Int) -> Int = { $0 * $1 }
print(multiply(4, 5)) // Output: 20

Trailing Closure Syntax

When a function’s last parameter is a closure, you can use trailing closure syntax:

func performOperation(_ operation: (Int, Int) -> Int, on a: Int, and b: Int) -> Int {
    return operation(a, b)
}

let result = performOperation({ $0 + $1 }, on: 10, and: 20)
print(result) // Output: 30

// Using trailing closure syntax
let result2 = performOperation(on: 10, and: 20) { $0 * $1 }
print(result2) // Output: 200

Capturing Values

Closures can capture and store references to any constants and variables from their surrounding context:

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

let incrementByTen = makeIncrementer(incrementAmount: 10)
print(incrementByTen()) // Output: 10
print(incrementByTen()) // Output: 20

Escaping Closures

An escaping closure is a closure that’s called after the function it was passed to returns:

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
    }
}

Autoclosures

An autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments and when it’s called, it returns the value of the expression that’s wrapped inside of it:

func printResult(_ result: @autoclosure () -> String) {
    print("The result is: \(result())")
}

printResult("Hello, World!") // Output: The result is: Hello, World!

Closures in Array Methods

Closures are extensively used in Swift’s standard library, especially with collections:

let numbers = [1, 2, 3, 4, 5]

// Map
let squared = numbers.map { $0 * $0 }
print(squared) // Output: [1, 4, 9, 16, 25]

// Filter
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // Output: [2, 4]

// Reduce
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // Output: 15

Closure as a Property

Closures can be used as properties in classes or structures:

class Calculator {
    var operation: (Int, Int) -> Int = (+)
    
    func calculate(_ a: Int, _ b: Int) -> Int {
        return operation(a, b)
    }
}

let calc = Calculator()
print(calc.calculate(5, 3)) // Output: 8

calc.operation = (*)
print(calc.calculate(5, 3)) // Output: 15

Closures in Asynchronous Operations

Closures are often used in asynchronous programming:

func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
    DispatchQueue.global().async {
        // Simulating network request
        Thread.sleep(forTimeInterval: 2)
        completion(.success("Data fetched successfully"))
    }
}

fetchData { result in
    switch result {
    case .success(let data):
        print(data)
    case .failure(let error):
        print("Error: \(error.localizedDescription)")
    }
}

Conclusion

Closures are a powerful feature in Swift that allow you to write clean, concise, and expressive code. They capture and store references to constants and variables from their surrounding context, making them a great tool for encapsulating functionality and passing it around in your programs. From basic syntax to more advanced concepts like escaping closures and autoclosures, understanding how to use closures effectively can greatly enhance your Swift programming skills. Thanks for reading!