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.