Reading — step 1 of 7
Learn
Swift uses Automatic Reference Counting (ARC) — not garbage collection. The compiler inserts retain/release calls so reference-typed objects are deallocated when no one holds them. Most of the time it's invisible. The exception: reference cycles, which require careful handling.
ARC basics
For reference types (classes), Swift tracks how many references each instance has:
class User {
let name: String
init(name: String) {
self.name = name
print("\(name) created")
}
deinit {
print("\(name) deallocated")
}
}
func demo() {
let u = User(name: "Alice")
// u goes out of scope here — refcount drops to 0 — deinit runs
}
func main() {
demo()
// prints: Alice created / Alice deallocated
}
Value types (structs, enums) don't use ARC — they're copied on assignment, no shared ownership to track.
Strong references — the default
A normal property is a strong reference:
class Person {
var car: Car? // strong — Person owns Car
}
class Car {
var owner: Person? // strong — Car owns Person?? trouble incoming
}
Reference cycles — the classic ARC bug
let p = Person()
let c = Car()
p.car = c
c.owner = p
// now: p strongly references c. c strongly references p.
// Both refcounts are 2. They never reach 0.
// Both leak — even after p and c go out of scope.
No deallocation happens because the two objects keep each other alive. Memory leak.
weak references — break the cycle
Mark one direction weak to opt out of refcount bumps:
class Person {
var car: Car? // strong
}
class Car {
weak var owner: Person? // weak — doesn't bump refcount
}
Now c.owner doesn't keep p alive. When p's strong references go away, p is deallocated and c.owner automatically becomes nil.
weak references are always Optional because the target can become nil at any time.
unowned references — "I know it'll outlive me"
Like weak but non-optional. Use when you're certain the target lives at least as long as the reference holder:
class Customer {
let name: String
var card: CreditCard?
init(name: String) { self.name = name }
}
class CreditCard {
let number: String
unowned let customer: Customer // card can't exist without customer
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
}
If you're wrong and the target dies first, accessing unowned reference crashes (similar to force-unwrapping a nil Optional). Use sparingly.
Closures capture self strongly — careful
class NetworkClient {
func fetch(completion: @escaping (Data) -> Void) {
// ...
}
func loadProfile() {
fetch { data in
self.process(data) // captures self strongly!
}
}
}
If fetch's closure outlives the NetworkClient, the closure keeps it alive — possibly forever. Fix with a capture list:
fetch { [weak self] data in
self?.process(data) // weak — closure doesn't keep self alive
}
[weak self] and [unowned self] are the canonical patterns for callbacks that escape your method.
When to worry about ARC
In most Swift code, ARC is invisible. You worry when:
- Two classes hold each other → use
weakorunownedon one side - Closures escape (
@escaping) → use[weak self] - Long-lived parent ↔ short-lived child relationships
- Profiling shows leaks (Instruments → Allocations)
Value-type-heavy code (most Swift) sees ARC mostly via class members deeply nested. Structs and enums don't have these issues.
Common mistakes
- Forgetting
[weak self]in escaping closures — leaks and prevents deallocation. - Using
unownedwhen the target might die first — crash. - Marking value types
weak— compile error. weak/unowned are for class references only. - Trying to debug "leak" symptoms in Swift like in C++ — different tools. Use Instruments' Leaks and Allocations templates.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…