Lesson 93Advanced Topics

ARC & Memory Management

How ARC Works

ARC (Automatic Reference Counting) tracks how many references point to each class instance. When the count reaches zero, memory is automatically freed. This only applies to classes (reference types), not structs.

Reference Types

strong (default)

Keeps object alive

Increments ref count

weak

Optional, can become nil

Doesn't keep alive

unowned

Non-optional, must be valid

Crashes if nil

Strong Reference Cycle

Memory Leak Warning!

When two objects hold strong references to each other, neither can be deallocated. Use weak or unowned to break the cycle.

When to Use Each

  • strong - Parent owns child
  • weak - Delegate pattern, optional back-reference
  • unowned - Child references parent (always exists)

Closures & Capture Lists

Closures capture self strongly by default. Use [weak self] or [unowned self] to prevent retain cycles.

{ [weak self] in guard let self = self else { return } }

Debugging Tools

  • Xcode Memory Graph - Visualize object relationships
  • Instruments Leaks - Find memory leaks
  • deinit - Add print to verify deallocation
main.swift
// ARC - AUTOMATIC REFERENCE COUNTING
// Swift's memory management system

// ===== HOW ARC WORKS =====
class Person {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

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

// Reference count: 1
var person1: Person? = Person(name: "John")

// Reference count: 2
var person2: Person? = person1

// Reference count: 1
person1 = nil

// Reference count: 0 -> deinit called
person2 = nil  // "John is being deinitialized"

// ===== STRONG REFERENCE CYCLE =====
class Human {
    let name: String
    var apartment: Apartment?

    init(name: String) { self.name = name }
    deinit { print("Human \(name) deinitialized") }
}

class Apartment {
    let unit: String
    var tenant: Human?  // Strong reference - causes cycle!

    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) deinitialized") }
}

var john: Human? = Human(name: "John")
var apt: Apartment? = Apartment(unit: "4A")

john?.apartment = apt
apt?.tenant = john

// Memory leak! Neither deinit is called
john = nil
apt = nil

// ===== WEAK REFERENCES =====
// Break cycles - can become nil
class Employee {
    let name: String
    var department: Department?

    init(name: String) { self.name = name }
    deinit { print("Employee \(name) deinitialized") }
}

class Department {
    let name: String
    weak var manager: Employee?  // weak breaks the cycle!

    init(name: String) { self.name = name }
    deinit { print("Department \(name) deinitialized") }
}

var emp: Employee? = Employee(name: "Alice")
var dept: Department? = Department(name: "Engineering")

emp?.department = dept
dept?.manager = emp

emp = nil   // "Employee Alice deinitialized"
dept = nil  // "Department Engineering deinitialized"

// ===== UNOWNED REFERENCES =====
// Like weak, but never nil (unsafe if object deallocated)
class Customer {
    let name: String
    var card: CreditCard?

    init(name: String) { self.name = name }
    deinit { print("Customer \(name) deinitialized") }
}

class CreditCard {
    let number: String
    unowned let customer: Customer  // Always has a customer

    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card \(number) deinitialized") }
}

var bob: Customer? = Customer(name: "Bob")
bob?.card = CreditCard(number: "1234", customer: bob!)

bob = nil  // Both deinitialized properly

// ===== CLOSURE CAPTURE LISTS =====
class DataManager {
    var data = "Important Data"

    lazy var processData: () -> String = { [weak self] in
        guard let self = self else { return "No data" }
        return "Processed: \(self.data)"
    }

    // Without [weak self], this would cause a retain cycle
    lazy var badClosure: () -> String = {
        return "Data: \(self.data)"  // Strong capture!
    }

    deinit { print("DataManager deinitialized") }
}

var manager: DataManager? = DataManager()
print(manager?.processData() ?? "")
manager = nil  // Properly deinitialized

// ===== UNOWNED IN CLOSURES =====
class NetworkManager {
    var url = "https://api.example.com"

    lazy var fetch: () -> Void = { [unowned self] in
        print("Fetching from \(self.url)")
    }

    deinit { print("NetworkManager deinitialized") }
}

// ===== WHEN TO USE EACH =====
// strong (default): Own the object, keep it alive
// weak: Optional reference, may become nil
// unowned: Non-optional, always valid during your lifetime

// ===== CHECKING MEMORY =====
// Use Xcode Instruments > Leaks to find memory leaks
// Use Debug Memory Graph to visualize retain cycles

Try It Yourself!

Create a ViewController with a closure that captures self. First cause a retain cycle, then fix it with [weak self]!