Retain cycles are the most common source of memory leaks in Swift apps, especially with closures and delegates.
Swift manages class instances with Automatic Reference Counting. Every strong reference increments a count; when it drops to zero the instance's deinit runs and its memory is freed immediately and deterministically — there's no garbage collector.
The catch is reference cycles: if A holds B strongly and B holds A strongly, neither count reaches zero and both leak. Break cycles with non-owning references:
- `weak var` — doesn't keep the object alive and becomes
nilwhen it's freed, so it is always an optionalvar. Use it when the other object may go away first, e.g. adelegateor a child's reference to its parent. - `unowned` — doesn't keep the object alive and is non-optional, but accessing it after deallocation traps. Use only when the referenced object always outlives this one.
Closures are reference types and capture self strongly. A class that stores a closure that uses self forms a cycle. Break it with a capture list:
onTap = { [weak self] in
guard let self else { return }
self.count += 1
}Not every closure needs [weak self]: non-escaping closures, and escaping ones that are released after running (like a one-shot Task or animation), don't create lasting cycles.
Value types (structs, enums) aren't reference counted themselves, but any class references they contain are. Use Xcode's Memory Graph Debugger or Instruments' Leaks to find cycles.
Example
final class Owner {
let name: String
var pet: Pet?
var onRename: (() -> Void)?
init(name: String) { self.name = name }
deinit { print("Owner \(name) freed") }
}
final class Pet {
weak var owner: Owner? // weak breaks the Owner <-> Pet cycle
deinit { print("Pet freed") }
}
do {
let o = Owner(name: "Kim")
let p = Pet()
o.pet = p
p.owner = o
o.onRename = { [weak o] in print(o?.name ?? "gone") } // no self-cycle
o.onRename?() // Kim
}
// Owner Kim freed
// Pet freedRemember
- ARC frees an object when its last strong reference disappears
weakbecomes nil;unownedtraps if used after free- Stored closures capturing
selfneed[weak self]
Watch out for
- Using
unownedfor an object that can actually be deallocated first, which crashes at run time.