Skip to content

Memory Management

Swift uses Automatic Reference Counting (ARC) to manage memory for class instances, deallocating objects when no longer referenced. This document covers ARC, reference types, retain cycles, and memory optimization.

ARC Basics

ARC tracks strong references to class instances, deallocating them when the reference count reaches zero. Value types (structs, enums) are copied, not referenced.

Example: ARC in Action:

swift
class Person {
    let name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

var person: Person? = Person(name: "Alice")
person = nil // "Alice deallocated"

Reference Types

  • Strong References: Default, increment reference count.
  • Weak References: Do not increment count, become nil when deallocated.
  • Unowned References: Assume non-nil, do not increment count, crash if deallocated.

Example: Strong Reference:

swift
class Apartment {
    var tenant: Person?
}

let apt = Apartment()
apt.tenant = Person(name: "Bob")

Example: Weak Reference:

swift
class Apartment {
    weak var tenant: Person?
}

var person: Person? = Person(name: "Charlie")
let apt = Apartment()
apt.tenant = person
person = nil // tenant becomes nil, "Charlie deallocated"

Example: Unowned Reference:

swift
class CreditCard {
    unowned let owner: Person
    init(owner: Person) { self.owner = owner }
}

let owner = Person(name: "Dave")
let card = CreditCard(owner: owner)
// owner = nil // Would crash if accessed

Retain Cycles

Retain cycles occur when objects strongly reference each other, preventing deallocation.

Example: Retain Cycle:

swift
class Person {
    var apartment: Apartment?
    let name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

class Apartment {
    var tenant: Person?
    deinit { print("Apartment deallocated") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment()
alice?.apartment = apt
apt?.tenant = alice // Retain cycle
alice = nil // No deallocation
apt = nil // No deallocation

Fixing with Weak Reference:

swift
class Apartment {
    weak var tenant: Person?
    deinit { print("Apartment deallocated") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment()
alice?.apartment = apt
apt?.tenant = alice
alice = nil // "Alice deallocated"
apt = nil // "Apartment deallocated"

Closure Capture Lists

Closures can cause retain cycles by capturing self strongly. Use capture lists ([weak self], [unowned self]) to manage references.

Example: Closure Retain Cycle:

swift
class ViewController {
    var name = "View"
    var handler: (() -> Void)?
    
    func setup() {
        handler = {
            print(self.name) // Strong capture
        }
    }
    
    deinit { print("ViewController deallocated") }
}

var vc: ViewController? = ViewController()
vc?.setup()
vc = nil // Not deallocated due to retain cycle

Fixing with Weak Capture:

swift
func setup() {
    handler = { [weak self] in
        print(self?.name ?? "Nil")
    }
}

var vc: ViewController? = ViewController()
vc?.setup()
vc = nil // "ViewController deallocated"

Lazy Properties and ARC

Lazy properties can create retain cycles if they capture self.

Example:

swift
class DataManager {
    lazy var data: [Int] = {
        return [self.count] // Strong capture
    }()
    let count = 42
    deinit { print("DataManager deallocated") }
}

var manager: DataManager? = DataManager()
_ = manager?.data
manager = nil // Not deallocated

Fix with Unowned:

swift
lazy var data: [Int] = { [unowned self] in
    return [self.count]
}()

Memory Optimization

  • Copy-on-Write: Value types like Array use copy-on-write to minimize copying.
  • Inout Parameters: Pass large structs inout to avoid copying.
  • Weak/Unowned: Use appropriately to reduce memory usage.

Example: Copy-on-Write:

swift
var array1 = [1, 2, 3]
var array2 = array1 // Shares storage
array2.append(4) // Copies storage
print(array1) // [1, 2, 3]
print(array2) // [1, 2, 3, 4]

Debugging Memory Issues

Use Xcode’s Instruments (Memory Graph Debugger, Leaks) to identify retain cycles and leaks.

Example Workflow:

  1. Run app in Xcode.
  2. Use Debug Memory Graph to visualize references.
  3. Check for unexpected strong references.

Best Practices

  • Weak for Optionals: Use weak for references that can become nil.
  • Unowned for Non-Nil: Use unowned when references are guaranteed.
  • Capture Lists: Always consider closure captures.
  • Deinit Testing: Ensure deinit is called as expected.
  • Minimize Strong References: Reduce object interdependencies.
  • Profile Regularly: Use Instruments for large projects.

Troubleshooting

  • Retain Cycles: Check for strong references in closures or properties.
  • Crashes on Unowned: Verify unowned references are valid.
  • Memory Leaks: Use Instruments to trace unreleased objects.
  • Unexpected Deallocation: Ensure weak references aren’t overused.
  • Performance: Optimize copy-on-write for large collections.

Example: Comprehensive Memory Management

swift
class Library {
    var books: [Book] = []
    weak var librarian: Person?
    deinit { print("Library deallocated") }
}

class Book {
    unowned let library: Library
    let title: String
    init(library: Library, title: String) {
        self.library = library
        self.title = title
    }
    deinit { print("Book \(title) deallocated") }
}

class Person {
    let name: String
    var handler: (() -> Void)?
    
    init(name: String) {
        self.name = name
        setup()
    }
    
    func setup() {
        handler = { [weak self] in
            print("Handler for \(self?.name ?? "nil")")
        }
    }
    
    deinit { print("\(name) deallocated") }
}

var library: Library? = Library()
var person: Person? = Person(name: "Eve")
library?.librarian = person
library?.books.append(Book(library: library!, title: "Swift Guide"))

person?.handler?() // "Handler for Eve"
person = nil // "Eve deallocated"
library = nil // "Library deallocated", "Book Swift Guide deallocated"

Released under the MIT License.