Swift Memory Management

Understanding how Swift manages memory automatically

🧠 What is Memory Management?

Swift automatically manages memory using Automatic Reference Counting (ARC). ARC tracks and manages your app's memory usage by automatically freeing up memory when objects are no longer needed.


// Swift automatically manages this object's memory
class Person {
    let name: String
    init(name: String) { self.name = name }
}

let person = Person(name: "John") // Memory allocated
// Memory automatically freed when person goes out of scope
                                    

Key Memory Concepts

ðŸ”Ē

Reference Counting

ARC counts references to objects

var ref1: Person? = Person(name: "Alice") // Count: 1
var ref2 = ref1 // Count: 2
ref1 = nil // Count: 1
ref2 = nil // Count: 0, memory freed
💊

Strong References

Default references that keep objects alive

class Dog {
    var owner: Person // Strong reference
    init(owner: Person) { self.owner = owner }
}
ðŸŠķ

Weak References

Don't keep objects alive, prevent cycles

class Dog {
    weak var owner: Person? // Weak reference
    init(owner: Person) { self.owner = owner }
}
🔄

Reference Cycles

When objects reference each other

// Avoid cycles with weak references
class Parent {
    var children: [Child] = []
}
class Child {
    weak var parent: Parent?
}

ðŸ”đ Strong vs Weak References

Understanding the difference prevents memory leaks:

class Owner {
    var pet: Pet?
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

class Pet {
    weak var owner: Owner? // Weak to prevent cycle
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

// Usage
var john: Owner? = Owner(name: "John")
var buddy: Pet? = Pet(name: "Buddy")

john?.pet = buddy
buddy?.owner = john

john = nil // Owner can be freed because pet uses weak reference

ðŸ”đ Unowned References

Use when you know the reference will never be nil:

class CreditCard {
    let number: String
    unowned let customer: Customer // Always has a customer
    
    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
}

class Customer {
    let name: String
    var card: CreditCard?
    
    init(name: String) {
        self.name = name
    }
}

// Usage
let customer = Customer(name: "Alice")
customer.card = CreditCard(number: "1234", customer: customer)

ðŸ”đ Closures and Memory

Closures can create reference cycles too:

class ViewController {
    var name = "Main"
    
    func setupButton() {
        // Strong reference cycle - BAD
        button.onTap = {
            print(self.name) // Captures self strongly
        }
        
        // Weak reference - GOOD
        button.onTap = { [weak self] in
            print(self?.name ?? "Unknown")
        }
        
        // Unowned reference - GOOD (if self always exists)
        button.onTap = { [unowned self] in
            print(self.name)
        }
    }
}

ðŸ”đ Memory Best Practices

Follow these guidelines for efficient memory usage:

✅ Do:

  • Use weak references for delegates and parent-child relationships
  • Use unowned references when you're sure the reference won't be nil
  • Use capture lists in closures to avoid cycles
  • Set references to nil when done with objects

❌ Don't:

  • Create strong reference cycles between objects
  • Ignore memory warnings in your apps
  • Keep unnecessary strong references to large objects

🧠 Test Your Knowledge

What type of reference should you use for a delegate property?