Closures

Swift · Essentials · 12 min

Closures power collection methods, completion handlers and SwiftUI, so you'll read them in nearly every file.

A closure is an unnamed function that can capture values from its surrounding scope. The full syntax is { (a: Int, b: Int) -> Bool in a < b }, but Swift lets you shorten it a lot:

  • Types are inferred from context: { a, b in a < b }.
  • Single-expression closures return implicitly.
  • Shorthand argument names: { $0 < $1 }.
  • An operator can stand in: sorted(by: <).
  • A trailing closure moves the last closure argument after the parentheses: names.map { $0.uppercased() }. Several trailing closures can follow, the later ones labeled.

Closures capture variables by reference: a closure that increments a captured var keeps it alive and sees later changes. Use a capture list [x] to capture the current value instead.

A closure parameter is non-escaping by default — it can only be called before the function returns, which lets the compiler optimize and lets you use self implicitly. If the function stores the closure or calls it later (for example a completion handler), mark the parameter @escaping. Escaping closures that capture a class instance's self can create retain cycles, covered in the ARC lesson.

Closures are reference types: two variables holding the same closure share its captured state.

Example

let names = ["Zoe", "adam", "Maya"]
let sorted = names.sorted { $0.lowercased() < $1.lowercased() }
print(sorted)                       // ["adam", "Maya", "Zoe"]

func makeCounter() -> () -> Int {
    var count = 0
    return { count += 1; return count }   // captures count
}
let next = makeCounter()
_ = next()
print(next())                       // 2

var handlers: [() -> Void] = []
func register(_ h: @escaping () -> Void) { handlers.append(h) }

var level = 1
register { [level] in print("captured", level) }
register { print("live", level) }
level = 5
handlers.forEach { $0() }           // captured 1, live 5

Remember

  • Trailing closures and $0 keep calls short
  • Closures capture variables, not values — use [x] to snapshot
  • Stored or delayed closures must be @escaping

Watch out for

  • Writing $0-heavy closures that span many lines; name the parameters when it gets long.

Check yourself

  1. Why must a completion handler stored in an array be marked @escaping?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/