iosNudge

How does ARC work in Swift?

MediumAsked oftenMemory & ARCSwift 3 min read

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

ARCTracing GC (Java, Kotlin, C#)
When memory is freedImmediately at count 0Later, when the GC runs
PausesNo GC pausesPossible pauses
CostSmall cost on every retain/releaseCost during collection
Reference cyclesLeak, you must break themCollected automatically
deinit timingPredictableUnpredictable

Strong, weak and unowned

  • Strong (default): increases the count. Keeps the object alive.
  • Weak: does not keep it alive. Becomes nil when 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 final and 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 Unmanaged and must call takeRetainedValue() or takeUnretainedValue().
  • What was MRC? Manual Reference Counting in old Objective-C. You wrote retain, release and autorelease yourself. 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 deinit run? 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.