← Swift EnglishChapter 12 of 13

Memory Management

## Learning Objectives - Understand ARC basics - Work with weak and unowned references - Avoid retain cycles - Master memory management patterns ## Automatic Reference Counting ### How ARC Works Swift uses Automatic Reference Counting (ARC) to manage memory: ```swift class Person { var name: String init(name: String) { self.name = name } deinit { print("\(name) is being deallocated") } } var person1: Person? var person2: Person? var person3: Person? person1 = Person(name: "Alice") // Reference count: 1 person2 = person1 // Reference count: 2 person3 = person1 // Reference count: 3 person1 = nil // Reference count: 2 person2 = nil // Reference count: 1 person3 = nil // Reference count: 0 -> DEALLOCATED ``` ### Reference Counting Rules | Action | Effect | |--------|--------| | Assign to variable | +1 reference | | Variable goes out of scope | -1 reference | | Assign nil | -1 reference | | Copy reference | +1 reference | ## Strong References ### Default Behavior ```swift class Teacher { var name: String init(name: String) { self.name = name } deinit { print("\(name) deallocated") } } let teacher = Teacher(name: "Mr. Smith") // Strong reference by default ``` ### Class Instance Lifecycle ```swift class Customer { let name: String init(name: String) { self.name = name } deinit { print("\(name) is being deallocated") } } func createCustomer() { let customer = Customer(name: "Alice") print("\(customer.name) created") } createCustomer() // Output: // Alice created // Alice is being deallocated ``` ## Weak References ### Declaration ```swift weak var delegate: SomeDelegate? ``` ### Use Case: Delegates ```swift protocol ParentDelegate: AnyObject { func childDidUpdate() } class Parent { weak var delegate: ParentDelegate? func update() { delegate?.childDidUpdate() } } class Child { var parent: Parent? init() { parent = Parent() parent?.delegate = self } deinit { print("Child deallocated") } } extension Child: ParentDelegate { func childDidUpdate() { print("Updated!") } } var child: Child? = Child() child = nil // Child deallocated properly - no retain cycle ``` ### Weak Reference Rules - Always optional (`var`) - Set to `nil` when referenced object deallocates - Use for delegates and parent references ## Unowned References ### Unowned Declaration ```swift unowned var reference: SomeType ``` ### Use Case: Captured References ```swift class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { print("Customer \(name) deallocated") } } class CreditCard { let number: Int unowned let owner: Customer init(number: Int, owner: Customer) { self.number = number self.owner = owner } deinit { print("Card \(number) deallocated") } } var customer: Customer? = Customer(name: "Alice") customer?.card = CreditCard(number: 1234, owner: customer!) // CreditCard's unowned owner doesn't increase retain count customer = nil // Customer deallocated, then CreditCard deallocated ``` ### unowned vs weak | Feature | weak | unowned | |---------|------|---------| | Type | Optional | Non-optional | | Value when deallocated | nil | Crashes if accessed | | Use when | Can be nil | Never nil | | Typical use | Delegates | Parent/child | ## Retain Cycles ### What is a Retain Cycle? ```swift class Person { var name: String var friend: Person? // Strong by default init(name: String) { self.name = name } deinit { print("Person \(name) deallocated") } } var alice: Person? var bob: Person? alice = Person(name: "Alice") bob = Person(name: "Bob") alice?.friend = bob // Alice -> Bob (strong) bob?.friend = alice // Bob -> Alice (strong) // Retain cycle: Alice <-> Bob // Neither will ever be deallocated! ``` ### Breaking the Cycle ```swift class Person { var name: String weak var friend: Person? // weak breaks the cycle init(name: String) { self.name = name } deinit { print("Person \(name) deallocated") } } ``` ## Closure Captures ### Closures and References ```swift class ClosureHandler { var value = 10 lazy var closure: () -> Int = { return self.value // Strong capture of self } } ``` ### Capture Lists ```swift class Handler { var value = 10 lazy var weakClosure: () -> Int = { [weak self] in return self?.value ?? 0 // Weak capture } lazy var unownedClosure: () -> Int = { [unowned self] in return self.value // Unowned capture } } ``` ### Why Capture Lists? ```swift class MyViewController { var data: [String] = [] func loadData() { // Without capture list - RETAIN CYCLE! // completion: () -> Void = { self.data = [...] } // With capture list - safe API.fetch { [weak self] result in self?.data = result } } } ``` ## Memory Management Patterns ### Delegate Pattern ```swift protocol ManagerDelegate: AnyObject { func didComplete() } class Manager { weak var delegate: ManagerDelegate? } ``` ### Parent-Child References ```swift class Parent { var children: [Child] = [] func addChild(_ child: Child) { children.append(child) child.parent = self // Strong } } class Child { weak var parent: Parent? // Weak - prevents cycle } ``` ### Data Source Pattern ```swift class TableViewCell { weak var dataSource: TableViewDataSource? } ``` ## Debugging Memory Issues ### Memory Graph Debugger (Xcode) 1. Run app in debug mode 2. Debug > Debug Memory Graph 3. Look for cycles and leaks ### Instruments - Allocations instrument - Leaks instrument - Core Allocation instrument ### Common Symptoms - Memory keeps growing - deinit never called - App using more RAM than expected ## Best Practices ### Do ```swift // Use weak for delegates weak var delegate: MyDelegate? // Use weak/weak self in closures someAsyncCall { [weak self] result in self?.handle(result) } // Use unowned when safe lazy var closure: () -> String = { [unowned self] in return self.id } ``` ### Don't ```swift // Don't create strong reference cycles class A { var b: B? } class B { var a: A? // Cycle if both strongly reference each other } // Don't use unowned when optional // BAD: unowned var optionalRef: String? (compiler error) ``` ## Value Types vs Reference Types ### Value Types (Struct, Enum) ```swift struct Point { var x: Int var y: Int } var p1 = Point(x: 1, y: 2) var p2 = p1 // Copy! No reference counting p2.x = 10 print(p1.x) // 1 (unchanged) print(p2.x) // 10 ``` ### Reference Types (Class) ```swift class PointClass { var x: Int var y: Int init(x: Int, y: Int) { self.x = x self.y = y } } var p1 = PointClass(x: 1, y: 2) var p2 = p1 // Same reference! p2.x = 10 print(p1.x) // 10 (changed!) print(p2.x) // 10 ``` ## Summary - ARC manages memory automatically for class instances - Strong references increase retain count - Weak references (`weak var`) don't increase count, can be nil - Unowned references (`unowned var`) don't increase count, never nil - Retain cycles occur with strong circular references - Use weak for delegates and parent references - Use capture lists in closures: `[weak self]` or `[unowned self]` - Value types (structs) are copied, not reference-counted - Reference types (classes) share references

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →