Swift Memory Management

Swift manages memory automatically using Automatic Reference Counting (ARC). ARC tracks how many parts of your code hold a reference to each class instance and frees the memory when nothing holds it anymore. Understanding ARC helps you avoid memory leaks and crashes — two of the most common bugs in iOS apps.

How ARC Works

Every class instance carries a reference count. Each time you assign the instance to a variable or constant, the count goes up by one. Each time that variable goes out of scope or is set to nil, the count drops by one. When the count reaches zero, ARC destroys the instance and frees the memory.

Diagram: Reference Counting

let a = Person("Alice")     → Reference count: 1
let b = a                   → Reference count: 2
let c = a                   → Reference count: 3

c = nil                     → Reference count: 2
b = nil                     → Reference count: 1
a = nil                     → Reference count: 0 → Destroyed ✓

Strong References (Default)

Every reference in Swift is strong by default. A strong reference keeps the instance alive as long as it exists.

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) created")
    }
    deinit {
        print("\(name) destroyed")
    }
}

var alice: Person? = Person(name: "Alice")
// Output: Alice created

var reference = alice    // Two strong references now

alice = nil              // Count drops to 1 — NOT destroyed yet
reference = nil          // Count drops to 0 — destroyed now
// Output: Alice destroyed

The Retain Cycle Problem

A retain cycle happens when two objects hold strong references to each other. Neither reaches zero, so ARC never frees them. This is a memory leak.

class Person {
    let name: String
    var apartment: Apartment?
    init(name: String) { self.name = name }
    deinit { print("\(name) destroyed") }
}

class Apartment {
    let unit: String
    var tenant: Person?   // ← Strong reference back to Person
    init(unit: String) { self.unit = unit }
    deinit { print("Apt \(unit) destroyed") }
}

var bob: Person?    = Person(name: "Bob")
var apt: Apartment? = Apartment(unit: "4B")

bob!.apartment = apt   // Person → Apartment (strong)
apt!.tenant    = bob   // Apartment → Person (strong)

bob = nil   // Count still 1 (Apartment holds it) — NOT destroyed
apt = nil   // Count still 1 (Person holds it)    — NOT destroyed
// No "destroyed" messages — memory is leaked!

Diagram: Retain Cycle

    [Person: Bob] ←────── strong ──────┐
         |                             |
         └──── strong ────→ [Apartment: 4B]

Both variables set to nil, but they keep each other alive.
Neither reaches count = 0. Memory is never freed. ← LEAK

Fixing With weak References

A weak reference does not increase the reference count. It always has an optional type because the object it points to can be deallocated at any time, setting the reference to nil automatically.

class Apartment {
    let unit: String
    weak var tenant: Person?   // ← weak: does not hold Person alive
    init(unit: String) { self.unit = unit }
    deinit { print("Apt \(unit) destroyed") }
}

var bob: Person?    = Person(name: "Bob")
var apt: Apartment? = Apartment(unit: "4B")

bob!.apartment = apt
apt!.tenant    = bob   // weak — does not increase Bob's count

bob = nil   // Bob's count → 0 → destroyed immediately
// Output: Bob destroyed

apt = nil   // Apt's count → 0 → destroyed
// Output: Apt 4B destroyed

Fixing With unowned References

An unowned reference also does not increase the count, but it is non-optional. Use it only when you know the referenced object will always outlive the reference holder. Accessing an unowned reference after the object is gone crashes the app.

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) { self.name = name }
    deinit { print("\(name) destroyed") }
}

class CreditCard {
    let number: String
    unowned let owner: Customer   // Card can't exist without a Customer
    init(number: String, owner: Customer) {
        self.number = number
        self.owner  = owner
    }
    deinit { print("Card \(number) destroyed") }
}

var customer: Customer? = Customer(name: "Carol")
customer!.card = CreditCard(number: "1234-5678", owner: customer!)

customer = nil
// Output:
// Carol destroyed
// Card 1234-5678 destroyed

Diagram: weak vs unowned

weak var reference: SomeClass?
  - Optional type
  - Becomes nil automatically when object is deallocated
  - Safe — always check before use
  - Use when either object can be nil

unowned let reference: SomeClass
  - Non-optional type
  - Does NOT become nil — crashes if accessed after dealloc
  - Use only when the referenced object always outlives this one

Capture Lists in Closures

Closures capture variables from their surrounding scope. A closure that captures self strongly inside a class creates a retain cycle between the closure and the class instance.

class Timer {
    var count = 0
    var tick: (() -> Void)?

    func start() {
        // [weak self] breaks the retain cycle
        tick = { [weak self] in
            guard let self = self else { return }
            self.count += 1
            print("Tick: \(self.count)")
        }
    }

    deinit { print("Timer destroyed") }
}

var t: Timer? = Timer()
t!.start()
t!.tick?()   // Output: Tick: 1
t!.tick?()   // Output: Tick: 2
t = nil
// Output: Timer destroyed  ← no leak

When to Use Each Reference Type

ReferenceCount EffectTypeUse When
strong (default)IncreasesNon-optionalYou need the object to stay alive
weakNo changeOptionalThe referenced object might become nil
unownedNo changeNon-optionalThe referenced object always outlives this one

Instruments: Finding Memory Leaks

Xcode's Instruments tool includes a Leaks profiler. Run your app through it and Instruments highlights objects that were allocated but never released — a clear sign of a retain cycle. Always run Leaks before shipping an app.

Summary

ARC automatically tracks and frees class instances by counting strong references. Retain cycles form when two objects hold strong references to each other, preventing deallocation. Break cycles with weak (for optional back-references) or unowned (when the referenced object is guaranteed to outlive the holder). Use [weak self] in closures inside classes to avoid capturing self strongly.

Leave a Comment

Your email address will not be published. Required fields are marked *