Skip to content

Automatic Reference Counting

Swift's Automatic Reference Counting (ARC) manages your app's memory by tracking object lifetimes and relationships. In most scenarios, ARC handles memory automatically, so you rarely need to intervene. It deallocates class instances when they're no longer in use, freeing up space efficiently.

ARC applies only to class instances, which are reference types. Value types like structs and enums are copied rather than referenced.

However, certain code relationships require explicit guidance for ARC to work optimally. This guide covers those cases and how to configure ARC properly. It's akin to ARC in Objective-C but tailored for Swift.

How ARC Works

When you instantiate a class, ARC allocates memory for its type info and properties. Once the instance is unused, ARC releases that memory to prevent leaks.

Critically, ARC avoids deallocating in-use instances to prevent crashes or invalid access. It achieves this by counting strong references—links from properties, variables, or constants to the instance. As long as one strong reference exists, the instance persists.

Assigning an instance to a property or variable creates a strong reference, maintaining the instance until the reference is removed (e.g., by setting to nil).

ARC in Action

Consider a simple Student class with a name property:

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

Now define three optional variables:

swift
var ref1: Student?
var ref2: Student?
var ref3: Student?

Create and assign an instance:

swift
ref1 = Student(name: "Alex Rivera")
// Prints "Alex Rivera is being initialized"

This establishes one strong reference. Adding more:

swift
ref2 = ref1
ref3 = ref1

Now there are three. Removing two:

swift
ref1 = nil
ref2 = nil

The instance survives due to the remaining reference. Only the final one triggers deallocation:

swift
ref3 = nil
// Prints "Alex Rivera is being deinitialized"

Strong Reference Cycles Between Class Instances

ARC excels with simple references but can fail if two instances reference each other strongly, forming a strong reference cycle. Neither deallocates, leaking memory.

Break cycles using weak or unowned references, which don't count toward the strong reference tally.

Creating a Cycle: An Example

Define Student and DormRoom classes:

swift
class Student {
    let name: String
    init(name: String) { self.name = name }
    var dorm: DormRoom?
    deinit { print("\(name) is being deinitialized") }
}

class DormRoom {
    let number: String
    init(number: String) { self.number = number }
    var occupant: Student?
    deinit { print("Dorm \(number) is being deinitialized") }
}

Set up variables:

swift
var alex: Student?
var room101: DormRoom?

Assign instances:

swift
alex = Student(name: "Alex Rivera")
room101 = DormRoom(number: "101")

Link them:

swift
alex!.dorm = room101
room101!.occupant = alex

Setting variables to nil fails to deallocate due to the cycle:

swift
alex = nil
room101 = nil

No deinit messages print—memory leaks.

Weak References

Use weak for references where the target may outlive the source (shorter lifetime). Mark with weak; it's always optional and auto-nil on deallocation.

Adapt the example: Make DormRoom.occupant weak:

swift
class DormRoom {
    let number: String
    init(number: String) { self.number = number }
    weak var occupant: Student?
    deinit { print("Dorm \(number) is being deinitialized") }
}

Re-link as before. Now:

swift
alex = nil
// Prints "Alex Rivera is being deinitialized"

The weak reference nils out, allowing deallocation. Then:

swift
room101 = nil
// Prints "Dorm 101 is being deinitialized"

Weak suits caching but not guaranteed-lifetime scenarios.

Unowned References

Use unowned for equal or longer lifetimes; it's non-optional and assumes validity. Mark with unowned. Accessing a deallocated unowned reference crashes.

Example: Client and LoyaltyCard. Cards always tie to clients:

swift
class Client {
    let name: String
    var card: LoyaltyCard?
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deinitialized") }
}

class LoyaltyCard {
    let id: UInt64
    unowned let client: Client
    init(id: UInt64, client: Client) {
        self.id = id
        self.client = client
    }
    deinit { print("Card #\(id) is being deinitialized") }
}

Assign:

swift
var client: Client?
client = Client(name: "Alex Rivera")
client!.card = LoyaltyCard(id: 9876_5432_1098_7654, client: client!)

Set to nil:

swift
client = nil
// Prints "Alex Rivera is being deinitialized"
// Prints "Card #9876543210987654 is being deinitialized"

Unowned Optional References

Unowned can wrap optionals for flexible lifetimes. Example: Department and Class with linked courses:

swift
class Department {
    var name: String
    var classes: [Class]
    init(name: String) {
        self.name = name
        self.classes = []
    }
}

class Class {
    var name: String
    unowned var department: Department
    unowned var nextClass: Class?
    init(name: String, in department: Department) {
        self.name = name
        self.department = department
        self.nextClass = nil
    }
}

Usage:

swift
let dept = Department(name: "Biology")
let basics = Class(name: "Intro to Cells", in: dept)
let advanced = Class(name: "Genetics", in: dept)
basics.nextClass = advanced
dept.classes = [basics, advanced]

Ensure manual cleanup to avoid invalid access.

Unowned with Implicitly Unwrapped Optionals

For mutual non-optional dependencies post-init, pair unowned with implicitly unwrapped optionals (!).

Example: Nation and Capital:

swift
class Nation {
    let name: String
    var capital: Capital!
    init(name: String, capitalName: String) {
        self.name = name
        self.capital = Capital(name: capitalName, nation: self)
    }
}

class Capital {
    let name: String
    unowned let nation: Nation
    init(name: String, nation: Nation) {
        self.name = name
        self.nation = nation
    }
}

Usage:

swift
var nation = Nation(name: "Canada", capitalName: "Ottawa")
print("\(nation.name)'s capital is \(nation.capital.name)")
// Prints "Canada's capital is Ottawa"

This satisfies two-phase initialization while avoiding cycles.

Strong Reference Cycles for Closures

Closures can cycle with instances if they capture self strongly (e.g., via self.property).

Example: WebElement with HTML renderer:

swift
class WebElement {
    let tag: String
    let content: String?

    lazy var toHTML: () -> String = {
        if let content = self.content {
            return "<\(self.tag)>\(content)</\(self.tag)>"
        } else {
            return "<\(self.tag) />"
        }
    }

    init(tag: String, content: String? = nil) {
        self.tag = tag
        self.content = content
    }

    deinit {
        print("\(tag) is being deinitialized")
    }
}

Custom closure:

swift
let header = WebElement(tag: "h1")
let fallback = "Default Content"
header.toHTML = {
    return "<\(header.tag)>\(header.content ?? fallback)</\(header.tag)>"
}
print(header.toHTML())
// Prints "<h1>Default Content</h1>"

Assign and nil:

swift
var section: WebElement? = WebElement(tag: "section", content: "Intro Text")
print(section!.toHTML())
// Prints "<section>Intro Text</section>"
section = nil
// No deinit—cycle!

The closure captures self strongly.

Resolving Closure Cycles

Use a capture list to weaken captures. Prefix with [weak self] or [unowned self] before parameters or in.

For equal lifetimes, use unowned:

swift
class WebElement {
    let tag: String
    let content: String?

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

    init(tag: String, content: String? = nil) {
        self.tag = tag
        self.content = content
    }

    deinit {
        print("\(tag) is being deinitialized")
    }
}

Now:

swift
var section: WebElement? = WebElement(tag: "section", content: "Intro Text")
print(section!.toHTML())
// Prints "<section>Intro Text</section>"
section = nil
// Prints "section is being deinitialized"

Always qualify self in closures to flag potential captures.

Released under the MIT License.