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)
}