Lesson 16 / 25
Automatic Reference Counting
Explain ARC, strong, weak and unowned references, and fix retain cycles.
How Swift frees class instances
Swift manages memory for class instances with Automatic Reference Counting (ARC). Each instance keeps a count of strong references; when the count reaches zero, the instance is deallocated immediately and its deinit runs. There is no garbage collector pausing your app, and value types (structs, enums) are not reference-counted in the same way. ARC's weakness is retain cycles: two objects holding strong references to each other (a view controller and a closure that captures it, or a parent and child that point to each other) never reach zero and leak. Break cycles with weak references, which do not increase the count and become nil automatically when the object is freed (so they are optional vars), or unowned references, which also do not increase the count but assume the object outlives the reference and crash if it does not. Typical rule: delegates are weak, closures stored by the object they capture use [weak self], and child-to-parent links are weak or unowned. Xcode's Memory Graph Debugger and Instruments' Leaks tool find cycles.
A retain cycle
Two objects holding strong references to each other are never freed; a weak link breaks the cycle.
A delegate cycle and the weak fix
The weak delegate lets both objects be freed.
protocol DownloaderDelegate: AnyObject { // AnyObject: class-only, so it can be weak
func downloadFinished(_ data: Data)
}
final class Downloader {
weak var delegate: DownloaderDelegate? // weak: does not keep the screen alive
var onProgress: ((Double) -> Void)?
func start() { /* ... */ }
}
final class ProductScreen: DownloaderDelegate {
let downloader = Downloader()
var progressText = ""
init() {
downloader.delegate = self
downloader.onProgress = { [weak self] value in // avoid screen -> downloader -> closure -> screen
self?.progressText = "\(Int(value * 100))%"
}
}
func downloadFinished(_ data: Data) { /* update UI */ }
deinit { print("ProductScreen freed") } // prints when the screen goes away
}Missing deinit output is a clue
Add a temporary deinit { print(...) } to a screen or controller and close it. If nothing prints, something still holds a strong reference, very often a closure capturing self.
Quick check: What happens to a weak reference when the object it refers to is deallocated?
- The app crashes
- It keeps the object alive
- It automatically becomes nil
- It points to a copy of the object
Answer
It automatically becomes nil — Weak references are zeroed automatically, which is why they must be optional variables.