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:
- Use
[unowned self]when the closure and the instance always refer to each other and are deallocated at the same time — the captured value is guaranteed to outlive the closure's use. - Use
[weak self]when the captured reference may becomenilat some point. A weak capture is always optional, so you'll typically unwrap it:guard let self else { return }.
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.