Closures
Closures are self-contained blocks of functionality that you can pass around and use in your code — much like blocks in C or lambdas in other languages. They can capture and store references to constants and variables from the context in which they're defined. Functions, which you just met, are actually a special, named kind of closure.
Closure expressions
A closure expression is a way to write an inline closure with a clear,
focused syntax. The general form puts the parameters and return type inside
the braces, separated from the body by the in keyword.
// { (parameters) -> ReturnType in statements }
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let sorted = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 < s2
})
print(sorted) // → ["Alex", "Barry", "Chris", "Daniella", "Ewa"]
A fully spelled-out closure passed to sorted(by:).
Inferring type from context
Because sorted(by:) expects a closure of type
(String, String) -> Bool, Swift can infer the parameter and
return types. You can drop them and the surrounding parentheses.
let sorted = names.sorted(by: { s1, s2 in return s1 < s2 })
Implicit returns
A single-expression closure can omit the return keyword — the
value of its one expression is returned automatically. (This same rule
applies to single-expression functions and computed properties.)
let sorted = names.sorted(by: { s1, s2 in s1 < s2 })
Shorthand argument names
Swift automatically provides shorthand argument names $0,
$1, $2, and so on for inline closures. If you use
them, you can omit the parameter list and the in keyword
entirely.
let sorted = names.sorted(by: { $0 < $1 })
Operator methods
Swift's String type defines its < operator as a
function with exactly the signature sorted(by:) wants. So you
can pass the operator itself and let Swift infer everything.
let sorted = names.sorted(by: <)
print(sorted.first!) // → Alex
Trailing closures
When a closure is the last argument to a function, you can write it as a trailing closure after the call's parentheses. If the closure is the only argument, you can omit the parentheses altogether. This is the form you see constantly in SwiftUI.
let sorted = names.sorted { $0 < $1 }
[1, 2, 3].forEach { print($0) } // → 1 2 3
Multiple trailing closures
If a function takes several closures, you can write them all as trailing
closures. The first uses no label; the rest are labeled. This keeps APIs
like SwiftUI's Button readable.
func loadPicture(
from url: String,
completion: (String) -> Void,
onFailure: () -> Void
) {
if url.hasPrefix("https") {
completion("image-data")
} else {
onFailure()
}
}
loadPicture(from: "https://example.com/pic") { data in
print("Loaded \(data)")
} onFailure: {
print("Couldn't load")
}
// → Loaded image-data
Capturing values
A closure can capture constants and variables from the surrounding context, then refer to and modify them inside its body even if the original scope no longer exists. The simplest example is a nested function that captures a counter.
func makeIncrementer(by amount: Int) -> () -> Int {
var total = 0
let incrementer = {
total += amount // captures `total` and `amount`
return total
}
return incrementer
}
let byTen = makeIncrementer(by: 10)
print(byTen()) // → 10
print(byTen()) // → 20
Closures are reference types
Functions and closures are reference types. When you assign a closure to two constants, both refer to the same closure and share the same captured state.
let alsoByTen = byTen
print(alsoByTen()) // → 30 — same captured `total` as byTen
print(byTen()) // → 40
Note: because closures capture by reference, a closure
stored on a class instance can create a reference cycle. You'll resolve
those with capture lists ([weak self]) in the ARC chapter.
Escaping closures
A closure escapes a function when it's stored and called
after the function returns — for example saved to a property or scheduled
for later. Mark such a parameter with @escaping. Non-escaping
is the default because it lets the compiler optimize and avoids accidental
retention.
var handlers: [() -> Void] = []
func store(_ handler: @escaping () -> Void) {
handlers.append(handler) // stored for later → must escape
}
store { print("Called later") }
handlers.first?() // → Called later
Watch out: inside an escaping closure you must refer to
instance members explicitly with self, which makes the capture
of self visible — a deliberate nudge to think about lifetimes
and cycles.
Autoclosures
An autoclosure automatically wraps an argument expression
in a closure, so it isn't evaluated until the closure is called. This
enables lazy evaluation while letting callers pass a plain expression. The
standard library uses it for &&, ||, and
assert.
func logIfTrue(_ condition: @autoclosure () -> Bool) {
if condition() { // expression evaluated only here
print("It was true")
}
}
logIfTrue(2 > 1) // → It was true (pass an expression, not a closure)
Tip: combine @autoclosure with
@escaping when you need to both defer evaluation and store the
expression for later. Use autoclosures sparingly — overuse makes code harder
to reason about because the timing of side effects becomes implicit.
Recap
Closures range from fully explicit expressions down to a single
$0 < $1. You learned how Swift trims the syntax with type
inference, implicit returns, and shorthand names; how trailing-closure
syntax powers fluent APIs; how closures capture and share state as reference
types; and how @escaping and @autoclosure control
when and where a closure runs.