A capture-list snapshot is not a deep object copy. An integer preserves its value at closure creation; a captured class reference still points to the same object. weak is a separate ownership choice that does not keep the target alive.
Mechanism and design choice
A closure capturing a mutable local variable can observe later changes. A capture list such as [number] stores the value at creation. A closure parameter stored for use after return needs @escaping, but escaping alone means neither asynchronous execution nor a memory leak. Primary source
Reproduce the experiment
Inside one function we create live and snapshot captures, then change number to 2. We capture a class reference before changing its object value to 3, and release an object captured weakly. Queue stores a callback, runs it once, and clears it to separate storage lifetime from invocation.
This standalone example was run on 2026-10-23 with Apple Swift 6.4 targeting arm64 macOS, in Swift 6 language mode. It needs no network or additional packages.
Download the complete Swift file
Run the command in the download directory. Preconditions stop execution if a check fails. Do not use -Ounchecked: it removes these checks.
swift -swift-version 6 swift-closure-capture-weak-self-escaping.swift
final class Box { var value = 1 }
final class Queue {
private var action: (() -> Int)?
func enqueue(_ action: @escaping () -> Int) { self.action = action }
func run() -> Int? {
let current = action
action = nil
return current?()
}
}
func experiment() {
var number = 1
let live = { number }
let snapshot = { [number] in number }
number = 2
precondition(live() == 2 && snapshot() == 1)
let box = Box()
let capturedReference = { [box] in box.value }
box.value = 3
precondition(capturedReference() == 3)
var temporary: Box? = Box()
let weakRead = { [weak box = temporary] in box?.value }
precondition(weakRead() == 1)
temporary = nil
precondition(weakRead() == nil)
let queue = Queue()
queue.enqueue(snapshot)
precondition(queue.run() == 1 && queue.run() == nil)
print("variable=2 snapshot=1 captured-object=3 weak-after-release=nil queued=1 then=nil")
}
experiment()
Recorded output and interpretation
variable=2 snapshot=1 captured-object=3 weak-after-release=nil queued=1 then=nil
The live capture returns 2, the numeric snapshot 1, and the captured object reference observes 3. The weak capture returns nil after deallocation. Queue returns 1 once and nil next. This Queue is a synchronous storage example, not an asynchronous scheduler.
Scope and limitations
Capture does not necessarily imply a heap allocation; the compiler can optimize storage. [weak self] is not mandatory for every closure: decide whether skipping work after the owner disappears is valid. Investigate direct and indirect ownership paths to establish an actual cycle.
What to check next
For stored callbacks, document who owns them, when they are cleared, and what cancellation means. Do not mechanically weaken a reference if the operation must keep its owner alive. The ARC experiment demonstrates breaking a real ownership cycle.
Sources and verification
- The Swift Programming Language: ClosuresSwift.org · Official documentation · Checked October 23, 2026Supports: Source for the implementation contract and explanation. The output was checked separately using the experiment included here.

