Reading — step 1 of 7
Learn
Swift uses Automatic Reference Counting (ARC) to manage memory for class instances. ARC is fast, deterministic — and has its own gotchas, mainly retain cycles. Apple's Swift book treats this as required reading for any class-heavy code.
What ARC does
For every class instance, Swift maintains a reference count:
class Person {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) deinitialized") }
}
var p1: Person? = Person(name: "Ada") // refcount = 1
var p2: Person? = p1 // refcount = 2
p1 = nil // refcount = 1
p2 = nil // refcount = 0 → deinit runs
// prints: "Ada deinitialized"
When the count hits zero, deinit runs and the memory is freed. Deterministic — no GC pause, no finalizer mystery.
Structs and enums are value types — no reference count. They're copied at boundaries; no ARC overhead.
The retain cycle problem
Two objects referencing each other create a cycle ARC can't break:
class Person {
let name: String
var apartment: Apartment?
init(name: String) { self.name = name }
deinit { print("\(name) deinit") }
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) { self.unit = unit }
deinit { print("unit \(unit) deinit") }
}
var john: Person? = Person(name: "John")
var unit: Apartment? = Apartment(unit: "4A")
john?.apartment = unit
unit?.tenant = john
john = nil // refcount of Person: 1 (apartment.tenant)
unit = nil // refcount of Apartment: 1 (person.apartment)
// Neither deinit runs — leak!
Both objects are kept alive only by each other. ARC can't tell — it just sees the count never dropped to zero.
weak references — break cycles
class Apartment {
let unit: String
weak var tenant: Person? // weak — doesn't increment refcount
init(unit: String) { self.unit = unit }
deinit { print("unit \(unit) deinit") }
}
Now setting tenant doesn't keep the Person alive. When the strong reference drops, Person deinits, and the weak reference becomes nil automatically.
weak requires var and Optional — the value can vanish at any time.
unowned — when nil isn't possible
class CreditCard {
let number: String
unowned let owner: Customer // unowned — non-optional, non-counting
init(number: String, owner: Customer) {
self.number = number
self.owner = owner
}
}
unowned is like weak but:
- NON-optional (you say it can never be nil)
- If you read it after the target deallocates → CRASH (or undefined behavior in older Swift)
- Use when the relationship guarantees one outlives the other (CreditCard < Customer)
Closures and capture
Closures hold strong references to anything they capture. Common cycle:
class Worker {
var onComplete: (() -> Void)?
var name: String = ""
func start() {
onComplete = {
print("\(self.name) done") // self captured strongly
}
}
deinit { print("deinit") }
}
let w = Worker()
w.start()
// w → onComplete → captures self → w. Cycle.
Fix with capture lists:
func start() {
onComplete = { [weak self] in
guard let self = self else { return }
print("\(self.name) done")
}
}
Or [unowned self] if you can guarantee Worker outlives the closure.
When to use which
- Strong (default) — when you OWN the relationship
- weak — for back-references (delegate patterns, parent pointers); allows the target to disappear
- unowned — back-references where lifetime is guaranteed (closures inside an object referencing self for the whole object's life)
The Apple guideline:
- Tree structures: parent → child strong, child → parent weak
- Delegates: protocol-typed properties almost always weak
- Closures capturing self: usually
[weak self]for safety
Tools for finding cycles
- Xcode Memory Graph Debugger — visualizes object references; circles surface immediately
- Instruments → Allocations / Leaks — runtime detection
- Print in
deinit— quickest manual check during development
Common mistakes
- Strong delegate references — the classic cycle. Always make delegates weak.
- Forgetting
[weak self]in closures stored on self — almost always a cycle. - Using
unownedwhenweakis safer — unowned crashes when the target's gone; weak just becomes nil. - Reading
weakproperties without checking nil — useguard letor Optional chaining. - Cycles through three or more objects — A → B → C → A. Same fix: weak/unowned somewhere in the chain.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…