Memory Management & ARC

Reviewed & published by Brayan K

Swift frees class instances automatically with ARC (Automatic Reference Counting). Learn how reference counting works, what a retain cycle is, and how weak, unowned, and [weak self] prevent memory leaks.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ ARC — Counting References

Each strong reference to a class instance raises its count; each one removed lowers it. At zero, ARC frees the instance and runs deinit. Watch the count rise and fall below.

// ARC (Automatic Reference Counting) frees a CLASS instance the moment
// nothing strongly references it anymore. deinit runs at that point.

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

var p1: Player? = Player(name: "Ada")   // count: 1  -> "Ada created"
var p2 = p1                              // count: 2  (same instance)
p1 = nil                                 // count: 1  (still alive)
p2 = nil                                 // count: 0  -> "Ada freed"

// Note: structs and enums are VALUE types — they're copied, not
// reference-counted, so ARC doesn't apply to them.

2️⃣ The Retain Cycle Problem

When two instances strongly reference each other, neither count can reach zero — so ARC never frees them and the memory leaks. The tell-tale sign is a deinit that never runs.

// A RETAIN CYCLE: two objects strongly reference each other, so neither
// count ever reaches zero — the memory leaks (deinit never prints).

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

class Apartment {
    let number: Int
    var tenant: Person?           // STRONG -> this causes the cycle
    init(number: Int) { self.number = number }
    deinit { print("Apt \(number) freed") }
}

var ada: Person? = Person(name: "Ada")
var apt: Apartment? = Apartment(number: 4)
ada!.apartment = apt              // Person -> Apartment
apt!.tenant = ada                 // Apartment -> Person  (cycle!)

ada = nil
apt = nil
// ❌ Nothing prints — both are leaked because they hold each other.

3️⃣ Breaking Cycles with weak

Make one side of the relationship weak. A weak reference doesn't raise the count and is automatically set to nil when its target is freed — so it must be an optional var. Now both objects can deallocate.

// FIX: make ONE side 'weak'. A weak reference doesn't bump the count and
// auto-nils when its target is freed — so it must be an optional var.

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

class Apartment {
    let number: Int
    weak var tenant: Person?      // ✅ weak breaks the cycle
    init(number: Int) { self.number = number }
    deinit { print("Apt \(number) freed") }
}

var ada: Person? = Person(name: "Ada")
var apt: Apartment? = Apartment(number: 4)
ada!.apartment = apt
apt!.tenant = ada

ada = nil      // Person's count -> 0 (apt's weak ref didn't hold it)
apt = nil      // Apartment's count -> 0
// Ada freed
// Apt 4 freed

4️⃣ Closures and [weak self]

Closures capture self strongly by default. If an object stores such a closure, you get a cycle. A capture list [weak self] makes self a weak optional inside the closure — unwrap it with guard let self = self.

// Closures capture 'self' STRONGLY by default. If an object stores a
// closure that captures self, you get a cycle. Fix it with [weak self].

class Downloader {
    var onDone: (() -> Void)?
    let id: Int
    init(id: Int) { self.id = id }

    func start() {
        // [weak self] makes 'self' optional inside the closure.
        onDone = { [weak self] in
            guard let self = self else { return }
            print("Download \(self.id) complete")
        }
    }
    deinit { print("Downloader \(id) freed") }
}

var d: Downloader? = Downloader(id: 7)
d?.start()
d?.onDone?()     // Download 7 complete
d = nil          // Downloader 7 freed  (no cycle, thanks to [weak self])

Your turn. Fill in the keyword that makes the parent reference non-retaining.

// 🎯 YOUR TURN — fill in each ___ then run it on SwiftFiddle.

class Node {
    let name: String
    // 1) Make 'parent' a weak reference so child -> parent doesn't leak.
    ___ var parent: Node?      // 👉 the keyword that avoids retaining
    var child: Node?
    init(name: String) { self.name = name }
    deinit { print("\(name) freed") }
}

var root: Node? = Node(name: "root")
var leaf: Node? = Node(name: "leaf")
root?.child = leaf
leaf?.parent = root            // weak -> no cycle

root = nil
leaf = nil

// ✅ Expected output (order may vary):
//    root freed
//    leaf freed

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

ReferenceCounts?Notes
strong (default)yeskeeps instance alive
weak var x: T?nooptional, auto-nils
unowned let x: Tnonon-optional, must outlive
[weak self]nobreaks closure cycle
struct / enumn/avalue type, copied

Mini-Challenge: Break a Cycle

No blanks this time — just a brief and an outline. Build two classes that reference each other, then make one side weak so both deinitialize.

// 🎯 MINI-CHALLENGE: break a cycle
// 1. Make classes  Author  and  Book.
//    - Book has  var author: Author?
//    - Author has  var book: Book?   <-- make this WEAK
// 2. Give both a deinit that prints "<thing> freed".
// 3. Create one of each, link them both ways, then set both vars to nil.
// 4. Confirm BOTH deinits print (no leak).
//
// ✅ Expected (order may vary):
//    Book freed
//    Author freed

// your code here

Practice quiz

What does ARC stand for in Swift?

  • Automatic Recompilation Cycle
  • Active Resource Control
  • Automatic Reference Counting
  • Allocated Region Cleanup

Answer: Automatic Reference Counting. ARC = Automatic Reference Counting, Swift's memory management for class instances.

ARC manages memory for which kind of types?

  • Class instances (reference types)
  • Structs and enums
  • Only Ints
  • Only optionals

Answer: Class instances (reference types). ARC tracks reference (class) types; value types like structs and enums are not reference-counted.

When does ARC deallocate a class instance?

  • After a fixed timeout
  • Only at app exit
  • When you call free()
  • When its strong reference count drops to zero

Answer: When its strong reference count drops to zero. ARC frees an instance the moment its strong reference count reaches zero.

What is a retain cycle (strong reference cycle)?

  • A loop that retains an array
  • Two instances holding strong references to each other so neither is ever freed
  • A CPU caching strategy
  • A type of optional

Answer: Two instances holding strong references to each other so neither is ever freed. When two objects strongly reference each other, their counts never reach zero, leaking memory.

Which keyword creates a reference that does NOT increase the reference count and becomes nil when the target is freed?

  • weak
  • strong
  • lazy
  • open

Answer: weak. A 'weak' reference doesn't retain its target and is automatically set to nil when the target is deallocated.

A 'weak' reference in Swift must be declared as what?

  • A constant (let)
  • A non-optional
  • An optional var
  • A computed property

Answer: An optional var. weak references must be optional vars because they can become nil at any time.

When is 'unowned' appropriate instead of 'weak'?

  • When the reference may outlive the target
  • When the referenced object is guaranteed to outlive the reference, so it never becomes nil
  • Always, it is safer
  • Only for value types

Answer: When the referenced object is guaranteed to outlive the reference, so it never becomes nil. Use unowned when the target is expected to always be alive during the reference's lifetime; it is non-optional and crashes if accessed after deallocation.

In a closure that captures self, how do you commonly avoid a retain cycle?

  • Use [strong self]
  • Mark the closure private
  • Use as? self
  • Use a capture list like [weak self]

Answer: Use a capture list like [weak self]. A capture list [weak self] (or [unowned self]) breaks the cycle between the object and the closure it stores.

After 'capture list [weak self]', what is the type of self inside the closure?

  • Self (non-optional)
  • Self? (optional) — you typically unwrap it
  • Int
  • Never

Answer: Self? (optional) — you typically unwrap it. [weak self] makes self optional inside the closure, so you usually write 'guard let self = self else { return }'.

Do structs and enums participate in ARC retain cycles?

  • Yes, always
  • Only if they contain Ints
  • No — they are value types and are copied, not reference-counted
  • Only inside classes

Answer: No — they are value types and are copied, not reference-counted. Value types are copied rather than referenced, so they don't create reference cycles on their own.

Continue this course

Frequently asked questions

What is ARC and what does it manage?

ARC stands for Automatic Reference Counting. It is Swift's memory-management system for class instances (reference types). Every time you create a strong reference to an instance its count goes up, and every time a strong reference goes away the count goes down. When the count hits zero, ARC deallocates the instance and runs its deinit. It happens automatically at compile time — you don't write retain/release calls yourself. Value types like structs and enums are copied rather than reference-counted, so ARC doesn't apply to them.

What is a retain cycle and why is it a problem?

A retain cycle (or strong reference cycle) happens when two or more class instances hold strong references to each other. Because each keeps the other's reference count above zero, neither ever reaches zero, so ARC can never free them — the memory leaks even after you stop using the objects. The classic symptom is a deinit that never prints. You break the cycle by making one of the references weak or unowned.

What is the difference between weak and unowned?

Both avoid increasing the reference count, so both break cycles. A weak reference must be an optional var because it automatically becomes nil when its target is deallocated — use it when the other object may legitimately disappear first. An unowned reference is non-optional and assumes the target outlives it; it never becomes nil, but accessing it after the target is gone crashes. Choose weak when the relationship is optional, unowned when one object is guaranteed to live at least as long as the other.

Why does a closure cause a retain cycle, and how does [weak self] fix it?

Closures capture the values they use, and by default they capture self strongly. If an object stores a closure (for example a completion handler) and that closure references self, the object holds the closure and the closure holds the object — a cycle. Adding a capture list [weak self] makes the captured self a weak optional that doesn't keep the object alive, breaking the cycle. Inside, you typically write 'guard let self = self else { return }' to safely unwrap it.

Do I need to worry about ARC with structs and enums?

No. Structs and enums are value types: they are copied when assigned or passed around rather than shared by reference, so they are not reference-counted and cannot form a retain cycle on their own. ARC only governs class instances. That said, if a struct contains a class instance, that contained reference is still managed by ARC.