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 1010.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 }