How it works
The explanation, with examples you can run.
Capturing variables
func makeCounter() -> () -> Int { var count = 0 return { // captures the variable count, which now outlives makeCounter count += 1 return count }} let next = makeCounter()_ = next(); _ = next()print(next()) // 3 var n = 1let byReference = { print("by reference:", n) }let byValue = { [n] in print("by value:", n) } // capture list: copy n nown = 2byReference() // by reference: 2byValue() // by value: 1By default a closure captures variables, not values: it sees later changes to n, and if it changes a captured var, the outside sees that too. The compiler moves such variables to the heap when a closure escapes with them. A capture list [n] evaluates n when the closure is created and stores that value as a constant inside it.
Escaping and non-escaping
A closure passed as a parameter is non-escaping unless the parameter is marked @escaping. A non-escaping closure can only be called during the function call (think map, filter, sorted(by:), withAnimation). The compiler can then keep it and its captures on the stack, and capturing self in it can't create a lasting cycle.
final class Downloader { private var handlers: [(String) -> Void] = [] func onFinish(_ handler: @escaping (String) -> Void) { handlers.append(handler) } // stored: must escape func finish() { handlers.forEach { $0("done") } } // forEach: non-escaping}An @escaping closure lives as long as whoever stores it, along with everything it captured strongly. In a class, using a property or method inside an escaping closure needs an explicit self. (or [self] in the capture list), so the capture is visible; for a struct, self is captured as a copy and implicit self is allowed.
Capture lists and references
For reference types, a capture list chooses the kind of reference: [weak self] (optional, becomes nil when the object is freed), [unowned self] (non-optional, traps if used after), or [self] (strong, but written out). Breaking reference cycles with these is covered in the iOS path's ARC topic. From the language side the key points are:
- A capture list entry is evaluated once, at creation.
[weak delegate = self.delegate]captures whatever the delegate was then. [weak self]only helps if no other strong path leads back: a captured property that itself holdsselfstill keeps it alive.- Values in a capture list are copied: a captured struct is a snapshot.
Closures and concurrency
In Swift 6, a closure that may run concurrently is @Sendable (or passed as sending): it may only capture values that are safe to share, and can't capture mutable local variables by reference. Task { } takes such a closure, which is why mutating a local var from inside a Task is a compile error rather than a data race. Closures also inherit the actor isolation of where they were written, which the concurrency topic explains.
Trailing closure syntax (items.sorted { $0 < $1 }, and several labelled trailing closures since Swift 5.3) is pure syntax: the closure is still a normal argument.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.