weak and unowned do not own their targets, but behave differently after deallocation. weak becomes nil; accessing a deallocated target through safe unowned causes a runtime error. Choose according to lifetime guarantees, not just optional syntax.
Mechanism and design choice
ARC combines compiler-generated ownership operations with runtime reference management. Objects in a strong cycle may remain after external owners disappear. An unowned reference can be optional, but the programmer must keep it pointing to a live object or explicitly set it to nil. Primary source
Reproduce the experiment
The first experiment makes two Nodes own each other, removes external strong references, observes survival weakly, and breaks one edge. The second uses a strong parent-to-child edge and a weak reverse edge. The final example manually clears an optional unowned reference while its Owner remains alive.
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-arc-weak-unowned.swift
final class Node { var next: Node? }
func breakCycle(_ node: Node) { node.next = nil }
weak var observedA: Node?
weak var observedB: Node?
do {
let a = Node()
let b = Node()
a.next = b
b.next = a
observedA = a
observedB = b
}
precondition(observedA != nil && observedB != nil)
breakCycle(observedA!)
precondition(observedA == nil && observedB == nil)
final class Owner { var child: Child? }
final class Child { weak var owner: Owner? }
weak var observedOwner: Owner?
weak var observedChild: Child?
do {
let owner = Owner()
let child = Child()
owner.child = child
child.owner = owner
observedOwner = owner
observedChild = child
}
precondition(observedOwner == nil && observedChild == nil)
final class Borrower {
unowned var owner: Owner?
init(_ owner: Owner) { self.owner = owner }
}
do {
let owner = Owner()
let borrower = Borrower(owner)
withExtendedLifetime(owner) {
precondition(borrower.owner === owner)
borrower.owner = nil
precondition(borrower.owner == nil)
}
}
print("cycle=retained broken=released weak=nil unowned-optional=manually-cleared")
Recorded output and interpretation
cycle=retained broken=released weak=nil unowned-optional=manually-cleared
The strong cycle survives its block but both observations become nil after one edge is broken. The weak-backed parent-child relationship is released when strong owners disappear. The optional unowned reference becomes nil because the example assigns nil, not because the runtime zeroes it.
Scope and limitations
This program does not access a dangling unowned reference and does not claim to have measured that crash. Exact deallocation at a particular source line is not a general guarantee. Optimization and lifetime extension matter; a missing deinit log alone does not prove a leak because caches or asynchronous work may legitimately retain objects.
What to check next
Draw the ownership graph and decide whether a reverse edge should own its target. Use weak when disappearance is valid; consider unowned only with a lifetime guarantee during access. Continue with closure capture when a stored closure retains its owner.
Sources and verification
- The Swift Programming Language: Automatic Reference CountingSwift.org · Official documentation · Checked October 23, 2026Supports: Source for the implementation contract and explanation. The output was checked separately using the experiment included here.

