Automatic Reference Counting

Swift tracks and frees the memory used by your class instances automatically through Automatic Reference Counting (ARC). Most of the time it just works and you never think about it — but when two objects refer to each other, memory can leak. This chapter explains how ARC works and how weak, unowned, and capture lists let you break those cycles.

How ARC works

Every time you create a new instance of a class, ARC allocates a chunk of memory to hold that instance's stored properties and type information. When the instance is no longer needed, ARC frees that memory so it can be reused. ARC must never deallocate an instance while it's still in use — so it counts the references to each instance.

Whenever you assign a class instance to a property, constant, or variable, that storage makes a strong reference to the instance. As long as at least one strong reference exists, ARC keeps the instance alive. When the last strong reference goes away, ARC deallocates the instance.

Note: ARC applies only to instances of classes (reference types). Structures and enumerations are value types — they're copied, not referenced, so they're never stored or passed by reference and don't need reference counting.

ARC in action

Here's a class that prints when an instance is created and destroyed, so we can watch ARC at work:

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

var ref1: Person?
var ref2: Person?
var ref3: Person?

ref1 = Person(name: "Ada")   // → Ada is being initialized
ref2 = ref1                       // now 2 strong references
ref3 = ref1                       // now 3 strong references

ref1 = nil                       // still alive (2 refs)
ref2 = nil                       // still alive (1 ref)
ref3 = nil                       // → Ada is being deinitialized

The instance survives until the very last strong reference is removed.

Notice that deinit doesn't run until all three references are set to nil. The optional variables are what hold the strong references here; clearing each one decrements the count.

Strong reference cycles between instances

The model above always reaches a reference count of zero. But it's possible to write code where an instance never reaches zero — a strong reference cycle. This happens when two class instances hold a strong reference to each other, keeping each other alive.

class Tenant {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?      // strong
    deinit { print("\(name) deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Tenant?           // strong
    deinit { print("Apartment \(unit) deinitialized") }
}

var alice: Tenant? = Tenant(name: "Alice")
var a4b: Apartment? = Apartment(unit: "4B")

alice!.apartment = a4b   // Apartment now also referenced by Alice
a4b!.tenant = alice      // Alice now also referenced by Apartment

alice = nil             // no deinit prints!
a4b = nil               // no deinit prints! both leaked

Each instance keeps the other alive — neither is ever freed.

After setting both variables to nil, the reference counts don't drop to zero, because each object still holds a strong reference to the other. The memory is leaked: it can never be reclaimed for the rest of the program.

Resolving cycles with weak references

A weak reference does not keep a strong hold on the instance it refers to, so it doesn't stop ARC from deallocating it. Use a weak reference when the other instance has a shorter or equal lifetime — that is, when it can legitimately become nil at some point during its lifetime. Because of this, a weak reference must be a var optional, and ARC automatically sets it to nil when the referenced instance is deallocated.

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Tenant?     // weak — breaks the cycle
    deinit { print("Apartment \(unit) deinitialized") }
}

Making the apartment's reference to its tenant weak.

Now Tenant strongly references its apartment, but Apartment only weakly references its tenant. Setting alice = nil removes the last strong reference to the tenant, so it's freed; that in turn removes the last strong reference to the apartment, so it's freed too. The cycle is broken.

Note: in systems that use garbage collection, weak references are sometimes used for simple caching, because nilling happens only when memory pressure triggers collection. With ARC, values are deallocated as soon as their last strong reference is removed, which makes weak references unsuitable for that purpose.

Resolving cycles with unowned references

An unowned reference, like a weak reference, doesn't keep a strong hold on the instance. Unlike a weak reference, you use it when the other instance has the same lifetime or a longer one — when the reference will always have a value. An unowned reference is therefore non-optional. ARC never sets it to nil.

Watch out: accessing an unowned reference after the instance it points to has been deallocated is a runtime error that traps your program. Use unowned only when you're certain the reference will outlive — or live exactly as long as — the holder.

class Customer {
    let name: String
    var card: CreditCard?       // strong; may be nil
    init(name: String) { self.name = name }
    deinit { print("\(name) deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer  // a card always has an owner
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) deinitialized") }
}

var john: Customer? = Customer(name: "John")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil   // → John deinitialized / Card #... deinitialized

A credit card can't exist without its customer, so it references the customer unowned.

A related case combines unowned with an implicitly unwrapped optional: when both properties must always have a value once initialization completes (each depends on the other), make one side unowned and the other an unowned or implicitly unwrapped optional so you can wire them up during init without a cycle.

Strong reference cycles for closures

Closures are reference types too. If you assign a closure to a property of a class instance, and that closure's body captures the instance — for example by referring to self or one of its properties — you create a strong reference cycle between the closure and the instance.

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        if let text = self.text {     // captures self strongly
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    deinit { print("\(name) deinitialized") }
}

The instance holds the closure; the closure captures the instance. A cycle.

Because the asHTML closure refers to self, it captures a strong reference to the HTMLElement. The element also holds a strong reference to the closure (via the property). Even after you stop using the element, neither is freed.

Resolving closure cycles with capture lists

You break a closure cycle with a capture list: a bracketed list at the start of the closure's parameter list that declares how specific values are captured. Mark a captured reference as weak or unowned instead of the default strong capture.

lazy var asHTML: () -> String = { [unowned self] in
    if let text = self.text {
        return "<\(self.name)>\(text)</\(self.name)>"
    } else {
        return "<\(self.name) />"
    }
}

A capture list with [unowned self] breaks the cycle.

Choose between weak and unowned in a capture list the same way you do for properties:

someAsyncWork { [weak self] result in
    guard let self else { return }   // safely bail if self is gone
    self.update(with: result)
}

A common pattern for escaping closures that may outlive their owner.

Tip: a closure only creates a cycle if the instance keeps a strong reference to the closure (or to something that does). A non-escaping closure passed to map, filter, or a synchronous helper finishes before the function returns, so it can't form a lasting cycle — you don't need a capture list there.

Wrapping up

ARC frees you from manual memory management for class instances, but you still own the relationships between objects. Reach for weak when a reference can validly disappear, unowned when it's guaranteed to stick around, and add a capture list whenever a stored or escaping closure refers to self. With those three tools, retain cycles become a solved problem.