← thecodex.expert · The Codex Family of Knowledge
Swift

Memory Management

Two class instances holding strong references to each other keep BOTH alive forever, even with zero other references anywhere in the program — a genuine, common memory leak, not a hypothetical edge case.

Swift 6 Retain cycles are real memory leaks Last verified:
Canonical Definition

Every class instance has a reference count, incremented each time a strong reference (the default kind) points to it, decremented when that reference goes away; the instance deallocates deterministically the moment its count reaches zero — no separate GC thread, no unpredictable pause. Structs and enums, being value types, aren't managed by ARC at all. The real, common failure mode is a retain cycle: two instances holding strong references to each other never reach a zero count, since each keeps the other alive, leaking both permanently even when nothing else in the program references either one. weak (an optional reference that automatically becomes nil when the referenced object deallocates) and unowned (a non-optional reference that assumes the object will always outlive it, crashing if accessed after deallocation) both break the cycle by making one side of the relationship not count toward the reference total.

ARC's basic mechanism

deinit runs the instant the reference count hits zero — deterministic, immediate, and directly observable, unlike a tracing garbage collector's unpredictable timing.

Swiftbasic_arc.swift
class Person {
    let name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deallocated") }
}

var person: Person? = Person(name: "Alice")
person = nil   // reference count hits 0 — deinit runs IMMEDIATELY, prints "Alice is being deallocated"

A real retain cycle: two classes leaking each other

Setting both to nil does NOT trigger deinit for either — alice still holds a strong reference to the apartment, and the apartment still holds a strong reference back to alice, so neither's count ever reaches zero.

Swiftretain_cycle_bug.swift
class Person {
    let name: String
    var apartment: Apartment?
    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

class Apartment {
    var tenant: Person?   // STRONG reference back to Person — this creates the cycle
    deinit { print("Apartment deallocated") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment()
alice?.apartment = apt
apt?.tenant = alice

alice = nil
apt = nil
// NEITHER deinit ever runs — both leaked forever, each keeping the other alive

Fixing it: weak on one side

tenant is now weak — it doesn't count toward Person's reference total, so setting alice to nil correctly drops Person's count to zero, deinit runs, and the cycle is broken.

Swiftfixed_with_weak.swift
class Apartment {
    weak var tenant: Person?   // weak — doesn't keep Person alive, automatically becomes nil
    deinit { print("Apartment deallocated") }
}
// with this fix, both deinit calls now run correctly when alice and apt are set to nil

Sources

1
Apple / Swift.org. The Swift Programming Language, "Automatic Reference Counting," docs.swift.org/swift-book.