# Memory & Performance ## Automatic Reference Counting (ARC) ```swift // Strong references (default) class Person { let name: String var apartment: Apartment? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String weak var tenant: Person? // Weak to break retain cycle init(unit: String) { self.unit = unit } deinit { print("Apartment \(unit) is being deinitialized") } } var john: Person? = Person(name: "John") var unit4A: Apartment? = Apartment(unit: "4A") john?.apartment = unit4A unit4A?.tenant = john // Setting to nil will properly deallocate both john = nil unit4A = nil ``` ## Weak and Unowned References ```swift // Weak - optional reference that doesn't keep object alive class ViewController: UIViewController { weak var delegate: ViewControllerDelegate? func performAction() { delegate?.didPerformAction() } } // Unowned - non-optional reference, assumes target outlives owner class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } } class CreditCard { let number: String unowned let customer: Customer // Customer always outlives card init(number: String, customer: Customer) { self.number = number self.customer = customer } } // Unowned optional (Swift 5+) class Department { var courses: [Course] = [] } class Course { unowned var department: Department unowned var nextCourse: Course? init(department: Department) { self.department = department } } ``` ## Capture Lists in Closures ```swift class DataManager { var data: [String] = [] func loadData() { // Strong reference cycle - DataManager won't be deallocated NetworkManager.fetch { response in self.data = response // self is captured strongly } // Weak self - breaks cycle NetworkManager.fetch { [weak self] response in guard let self = self else { return } self.data = response } // Unowned self - when self definitely outlives closure NetworkManager.fetch { [unowned self] response in self.data = response // Crashes if self is deallocated } // Capturing specific values let identifier = UUID() NetworkManager.fetch { [identifier] response in print("Request \(identifier) completed") } } } ``` ## Value Semantics ```swift // Structs provide automatic copy-on-write for collections struct User { var name: String var friends: [String] // Copy-on-write } var user1 = User(name: "Alice", friends: ["Bob"]) var user2 = user1 // Shallow copy user2.friends.append("Charlie") // Now triggers deep copy print(user1.friends) // ["Bob"] print(user2.friends) // ["Bob", "Charlie"] // Custom copy-on-write final class Storage { var value: T init(_ value: T) { self.value = value } } struct MyArray { private var storage: Storage<[Element]> init(_ elements: [Element] = []) { storage = Storage(elements) } var value: [Element] { get { storage.value } set { if !isKnownUniquelyReferenced(&storage) { storage = Storage(newValue) } else { storage.value = newValue } } } mutating func append(_ element: Element) { if !isKnownUniquelyReferenced(&storage) { storage = Storage(storage.value) } storage.value.append(element) } } ``` ## Performance Optimization ```swift // Use lazy properties for expensive computations class Report { let data: [DataPoint] lazy var summary: String = { // Expensive computation only when accessed data.map { $0.description }.joined(separator: "\n") }() init(data: [DataPoint]) { self.data = data } } // Avoid repeated type casting // Bad for item in items { if let user = item as? User { processUser(user) } } // Good let users = items.compactMap { $0 as? User } for user in users { processUser(user) } // Use contiguous storage // Slower - pointer indirection for each element let arrayOfClasses: [MyClass] = [MyClass(), MyClass()] // Faster - contiguous memory let arrayOfStructs: [MyStruct] = [MyStruct(), MyStruct()] // Avoid string concatenation in loops // Bad var result = "" for item in items { result += item.description // Allocates new string each time } // Good let result = items.map { $0.description }.joined() // Or var result = "" result.reserveCapacity(estimatedSize) for item in items { result.append(item.description) } ``` ## Collection Performance ```swift // Choose the right collection type // Array - ordered, random access O(1), append O(1) amortized let ordered: [Int] = [1, 2, 3] // Set - unique elements, contains O(1), no order let unique: Set = [1, 2, 3] // Dictionary - key-value pairs, lookup O(1) let mapping: [String: Int] = ["a": 1, "b": 2] // Use ContiguousArray for performance-critical code let contiguous = ContiguousArray(repeating: MyStruct(), count: 1000) // Reserve capacity for known sizes var numbers: [Int] = [] numbers.reserveCapacity(1000) for i in 0..<1000 { numbers.append(i) } // Use enumerated() instead of indices // Bad for i in 0..(_ value: T) { // Specialized versions generated } ``` ## Memory Warnings ```swift class ImageCache { private var cache: [String: UIImage] = [:] init() { NotificationCenter.default.addObserver( self, selector: #selector(clearCache), name: UIApplication.didReceiveMemoryWarningNotification, object: nil ) } @objc private func clearCache() { cache.removeAll() } deinit { NotificationCenter.default.removeObserver(self) } } ``` ## Best Practices - Use value types (structs) by default - Use weak references for delegates - Use unowned when lifetime is guaranteed - Always use capture lists in closures that reference self - Profile before optimizing (use Instruments) - Reserve collection capacity when size is known - Use lazy properties for expensive computations - Implement copy-on-write for custom types with reference storage - Handle memory warnings in iOS apps - Use autoreleasepool for memory-intensive loops - Choose appropriate collection types - Avoid premature optimization - measure first