Closures

10.1 Closure Syntax

let names = ["Chris", "Alex", "Ewa", "Barry"]

// Full form
names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 < s2 })
// Inferred types
names.sorted(by: { s1, s2 in s1 < s2 })
// Shorthand arguments
names.sorted(by: { $0 < $1 })
// Operator method
names.sorted(by: <)
// Trailing closure
names.sorted { $0 < $1 }
// Multiple trailing closures
UIView.animate(withDuration: 0.3) {
    view.alpha = 0
} completion: { _ in
    view.removeFromSuperview()
}

10.2 Capturing Values

func makeCounter() -> () -> Int {
    var count = 0
    return {
        count += 1          // captures count by reference
        return count
    }
}
let counter = makeCounter()
counter(); counter()       // 1, 2

var value = 10
let captureByValue = { [value] in print(value) }   // capture list: copies the current value
value = 20
captureByValue()           // prints 10

10.3 Escaping and Non-Escaping Closures

Closures are non-escaping by default (they can't outlive the call), which allows optimizations.

var handlers: [() -> Void] = []
func register(_ handler: @escaping () -> Void) {   // stored for later → @escaping
    handlers.append(handler)
}

class Downloader {
    var onComplete: ((Data) -> Void)?
    func start() {
        fetch { [weak self] data in                // avoid retain cycles in escaping closures
            guard let self else { return }
            self.onComplete?(data)
        }
    }
    func fetch(_ completion: @escaping (Data) -> Void) { /* ... */ }
}

10.4 Autoclosures

func logIfNeeded(_ condition: Bool, _ message: @autoclosure () -> String) {
    if condition { print(message()) }    // the message is only evaluated if needed
}
logIfNeeded(false, expensiveDescription())
// Used by assert(), ??, and &&

10.5 Closures and Functional Patterns

let compose = { (f: @escaping (Int) -> Int, g: @escaping (Int) -> Int) in { x in g(f(x)) } }
let addOneThenDouble = compose({ $0 + 1 }, { $0 * 2 })
addOneThenDouble(3)   // 8

let lazyValue: () -> Int = { print("computing"); return 42 }