How does ARC work in Swift?
TL;DR
ARC means Automatic Reference Counting. Every class instance has a count of strong references to it.
When a new strong reference is made, the count goes up. When one goes away, it goes down. At zero, the object is freed and deinit runs.
The compiler inserts these retain and release calls for you. ARC is not a garbage collector: it is deterministic, but it cannot break reference cycles by itself.
A simple lifecycle
final class Person {
let name: String
init(name: String) { self.name = name; print("\(name) created") }
deinit { print("\(name) freed") }
}
var a: Person? = Person(name: "Ana") // count = 1, "Ana created"
var b = a // count = 2
a = nil // count = 1
b = nil // count = 0, "Ana freed"The object lives exactly as long as someone holds a strong reference.
What ARC manages
- Class instances.
- Actor instances.
- Closures (their captured context is reference counted).
Structs and enums are not reference counted themselves. But if they contain class references, String, or Array storage, those parts are counted when the value is copied.
struct Order {
let customer: Person // copying Order retains this Person
let items: [String] // and this array buffer
}Compile time, not run time
The compiler adds retain and release calls in your code. There is no background thread that scans memory.
func greet(_ p: Person) {
print(p.name)
}
// The optimizer removes many retain/release pairs it can prove are not needed.Because of this, an object can be freed earlier than you expect: right after its last use, not at the end of the scope.
func demo() {
let p = Person(name: "Ben")
print(p.name)
// p may already be freed here, before the function ends
doOtherWork()
}Use withExtendedLifetime(p) { ... } if you really need it to stay alive.
ARC vs garbage collection
| ARC | Tracing GC (Java, Kotlin, C#) | |
|---|---|---|
| When memory is freed | Immediately at count 0 | Later, when the GC runs |
| Pauses | No GC pauses | Possible pauses |
| Cost | Small cost on every retain/release | Cost during collection |
| Reference cycles | Leak, you must break them | Collected automatically |
deinit timing | Predictable | Unpredictable |
Strong, weak and unowned
- Strong (default): increases the count. Keeps the object alive.
- Weak: does not keep it alive. Becomes
nilwhen the object is freed. - Unowned: does not keep it alive. Not optional. Crashes if used after the object is freed.
final class Apartment {
weak var tenant: Person? // does not keep tenant alive
}Under the hood (for senior interviews)
Each object has a header with an inline reference count. It tracks:
- the strong count,
- the unowned count,
- flags (for example, "is deiniting").
When you first create a weak reference, Swift moves the counts into a separate side table. The weak reference points to the side table, not the object. So the object's memory can be freed while weak references still exist. They read nil from the side table.
With unowned, the object is deinitialized at strong count 0, but its memory stays until the unowned count also reaches 0. That is how Swift can detect access to a dead unowned object and trap.
Thread safety
Retain and release are atomic. Two threads can copy the same reference at the same time without breaking the count.
But ARC does not make your object's properties thread-safe. Only the count is safe.
Performance tips
- Mark classes
finaland avoid unnecessary class wrappers. Fewer references, less counting. - Prefer structs with simple values in hot code.
- Avoid copying references inside tight loops when you can borrow them.
Common follow-up questions
- Does ARC work for Core Foundation types? Swift manages CF objects returned by annotated APIs. For unannotated ones you get
Unmanagedand must calltakeRetainedValue()ortakeUnretainedValue(). - What was MRC? Manual Reference Counting in old Objective-C. You wrote
retain,releaseandautoreleaseyourself. ARC replaced it in 2011. - What is an autorelease pool? An Objective-C mechanism that delays releases until the pool drains. Use
autoreleasepool { }in loops that create many Objective-C objects. - When exactly does
deinitrun? Right after the last strong reference goes away, on the thread that released it. - Can ARC cause leaks? Yes. Two objects holding strong references to each other never reach zero. That is a retain cycle.