Closures
Closures are blocks of code that can be passed around like variables. They are similar to lambdas or anonymous functions in other languages. Closures can capture variables from their surrounding code, and Swift manages the memory for you.
Global functions and nested functions are special types of closures:
- Global functions have names but don't capture values.
- Nested functions have names and can capture values from their parent function.
- Closure expressions are unnamed and can capture values from context.
Swift makes closures concise with features like:
- Type inference for parameters and returns.
- Implicit returns for single-line closures.
- Shorthand arguments like $0, $1.
- Trailing closure syntax.
Closure Expressions
Closure expressions let you write short, inline functions. They're useful for methods that take functions as arguments, like sorting arrays.
Swift's Array has a sorted(by:) method that sorts based on a closure you provide. It returns a new sorted array without changing the original.
Example array:
let fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]To sort in reverse alphabetical order, the closure should return true if the first string is greater than the second.
A full function version:
func reverseSort(_ str1: String, _ str2: String) -> Bool {
return str1 > str2
}
var sortedFruits = fruits.sorted(by: reverseSort)
// sortedFruits is ["Elderberry", "Date", "Cherry", "Banana", "Apple"]Basic Syntax
General form:
{ (parameters) -> returnType in
statements
}Inline example:
sortedFruits = fruits.sorted(by: { (str1: String, str2: String) -> Bool in
return str1 > str2
})Shorter on one line:
sortedFruits = fruits.sorted(by: { (str1: String, str2: String) -> Bool in return str1 > str2 })Type Inference
Swift infers types from context, so omit types and ->:
sortedFruits = fruits.sorted(by: { str1, str2 in return str1 > str2 })Implicit Return
For single-expression closures, omit return:
sortedFruits = fruits.sorted(by: { str1, str2 in str1 > str2 })Shorthand Arguments
Use $0, $1 for arguments, omit in:
sortedFruits = fruits.sorted(by: { $0 > $1 })Operator as Closure
Use the > operator directly:
sortedFruits = fruits.sorted(by: >)Trailing Closures
If the last argument is a closure and it's long, write it after the parentheses.
Example function:
func runTask(action: () -> Void) {
// task code
}Call with trailing closure:
runTask {
// action code
}For sorting:
sortedFruits = fruits.sorted() { $0 > $1 }If it's the only argument, omit parentheses:
sortedFruits = fruits.sorted { $0 > $1 }Example with map: Convert numbers to spelled-out strings.
Dictionary:
let numWords = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let values = [23, 45, 67]Map with trailing closure:
let spelled = values.map { (val) -> String in
var val = val
var result = ""
repeat {
result = numWords[val % 10]! + result
val /= 10
} while val > 0
return result
}
// spelled is ["TwoThree", "FourFive", "SixSeven"]For multiple closures, label after the first:
func fetchData(from source: Source, success: (Data) -> Void, failure: () -> Void) {
// code
}
fetchData(from: someSource) { data in
// handle success
} failure: {
// handle failure
}Capturing Values
Closures capture variables from their context, even after the context ends.
Example: A function that returns a closure to add a fixed amount.
func createAdder(addBy amount: Int) -> () -> Int {
var total = 0
func adder() -> Int {
total += amount
return total
}
return adder
}
let addFive = createAdder(addBy: 5)
addFive() // 5
addFive() // 10
let addThree = createAdder(addBy: 3)
addThree() // 3Each closure has its own captured total.
Closures as Reference Types
Closures are reference types, so assigning to another variable shares the same instance.
let alsoAddFive = addFive
alsoAddFive() // 15
addFive() // 20Escaping Closures
Mark @escaping if the closure outlives the function, like storing it.
Example:
var handlers: [() -> Void] = []
func addEscapingHandler(handler: @escaping () -> Void) {
handlers.append(handler)
}For classes, capture self explicitly to avoid cycles:
class ExampleClass {
var num = 0
func run() {
addEscapingHandler { self.num = 50 }
}
}Or use capture list:
addEscapingHandler { [self] in num = 50 }For structs, escaping closures can't mutate self.
Autoclosures
Autoclosures wrap expressions as closures automatically, delaying evaluation.
Example:
var queue = ["Item1", "Item2", "Item3"]
let provider = { queue.remove(at: 0) } // Doesn't remove yet
provider() // "Item1"Function version:
func process(itemProvider: @autoclosure () -> String) {
print("Processing \(itemProvider())")
}
process(item: queue.remove(at: 0)) // "Item2", removes nowFor escaping: Use @autoclosure @escaping.
var providers: [() -> String] = []
func collect(_ provider: @autoclosure @escaping () -> String) {
providers.append(provider)
}
collect(queue.remove(at: 0))