Reference Cycles: weak and unowned
Prerequisites: Memory management with ARC
A strong reference cycle happens when two class instances hold strong references to each other. Each keeps the other's count above zero, so neither is ever freed — a memory leak.
The problem
final class Owner {
let name: String
var pet: Pet?
init(name: String) { self.name = name }
deinit { print("\(name) freed") }
}
final class Pet {
let name: String
var owner: Owner? // strong: creates a cycle
init(name: String) { self.name = name }
deinit { print("\(name) freed") }
}
do {
let ada = Owner(name: "Ada")
let rex = Pet(name: "Rex")
ada.pet = rex
rex.owner = ada
}
print("End of scope")
End of scope
Neither deinit runs. The local constants are gone, but Ada still
holds Rex and Rex still holds Ada.
The fix: weak
Make one side of the relationship weak. A weak reference doesn't
increase the count, and it's automatically set to nil when the
instance it points to is freed — so it must be an optional var:
final class Pet {
let name: String
weak var owner: Owner? // weak: no cycle
init(name: String) { self.name = name }
deinit { print("\(name) freed") }
}
With that one change, the same code prints:
Ada freed
Rex freed
End of scope
The rule of thumb: the "parent" or "owner" holds its children strongly;
a child's reference back to its parent is weak. Delegates are almost
always weak for the same reason.
unowned
unowned also doesn't increase the count, but it isn't optional. Use
it only when the referenced instance is guaranteed to outlive the
reference — for example, a credit card that can never exist without its
customer. Accessing an unowned reference after the instance is freed
crashes, so weak is the safer default.
Cycles through closures
The most common cycle in real apps: an object stores a closure, and the
closure uses self:
final class Downloader {
var onComplete: (() -> Void)?
var fileCount = 0
func start() {
onComplete = { [weak self] in
guard let self else { return }
print("Downloaded \(self.fileCount) files")
}
}
}
Without [weak self], the closure would hold self strongly while
self holds the closure. The capture list [weak self] breaks the
cycle; guard let self then unwraps it for the rest of the closure.
You don't need [weak self] for closures that run and finish without
being stored — such as map or filter — because no cycle can form.
Finding leaks
Xcode's Memory Graph Debugger (the three-circle button in the debug bar) shows every live object and highlights leaks. Instruments' Leaks tool finds them during a profiling run.