Memory Management and ARC
25.1 How ARC Works
Automatic Reference Counting: every class instance tracks how many strong references point to it. When the count drops to zero, deinit runs and the memory is freed immediately (deterministic, unlike a GC).
ARC applies to class instances (and closures that capture references). Value types aren't reference counted (though their heap-backed storage, like arrays, is).
25.2 Retain Cycles and weak/unowned
strong (default) Always optional (var) Delegates, parent references, the referent may die first
❌ (crashes if accessed after deallocation) The referent always outlives the reference (same lifetime) Performance-critical, very rare
class Person {
let name: String
var apartment: Apartment?
init(name: String) { self.name = name }
deinit { print("\(name) deinitialized") }
}
class Apartment {
let unit: String
weak var tenant: Person? // weak breaks the cycle (it's always optional and becomes nil automatically)
init(unit: String) { self.unit = unit }
deinit { print("Apartment \(unit) deinitialized") }
}
class Customer {
var card: CreditCard?
}
class CreditCard {
unowned let customer: Customer // unowned: non-optional, assumes the referent outlives this object
init(customer: Customer) { self.customer = customer }
}
var ana: Person? = Person(name: "Ana")
var unit4A: Apartment? = Apartment(unit: "4A")
ana?.apartment = unit4A
unit4A?.tenant = ana
ana = nil; unit4A = nil // both deinitialized (no leak)25.3 Closure Capture Cycles
Non-escaping closures (like map) don't need [weak self]. Structured concurrency Tasks capture self strongly but finish. Long-running tasks and stored closures need care.
class ViewModel {
var onUpdate: (() -> Void)?
var title = "Home"
func bind() {
onUpdate = { [weak self] in // without [weak self]: self → closure → self
guard let self else { return }
print(self.title)
}
}
deinit { print("ViewModel freed") }
}25.4 Detecting Leaks
Xcode Memory Graph Debugger, Instruments (Leaks, Allocations), deinit logging, and weak references in tests to assert deallocation:
func testNoLeak() {
var vm: ViewModel? = ViewModel()
weak var weakVM = vm
vm?.bind()
vm = nil
XCTAssertNil(weakVM)
}