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 cyclesTry It Yourself!
Create a ViewController with a closure that captures self. First cause a retain cycle, then fix it with [weak self]!