Sobes.tech
Middle

How does automatic memory management (ARC) work in Swift?

sobes.tech AI

Answer from AI

ARC (Automatic Reference Counting) in Swift tracks and manages your application's memory usage. It automatically frees memory used by class instances when they are no longer needed, preventing memory leaks.

When a new class instance is created, ARC allocates a block of memory to store information about that instance. When the instance is no longer needed, ARC releases that memory so it can be used for other purposes.

ARC works by counting the number of strong references to each class instance. A strong reference is a reference that keeps the instance in memory. When the number of strong references to an instance drops to zero, ARC deallocates the associated memory.

There are three types of references in Swift that affect ARC:

  1. Strong References: Increase the reference count to an instance.
  2. Weak References: Do not increase the reference count. Used to avoid strong reference cycles when two class instances hold strong references to each other. A weak reference automatically becomes nil when the object it references is deallocated.
  3. Unowned References: Also do not increase the reference count. Used when you are sure that the referenced instance will always exist as long as the current instance exists. They cannot be nil. Accessing an unowned reference after the instance has been deallocated will cause a runtime error.

Example of a strong reference cycle:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) is being initialized")
    }

    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil // Person is not deallocated
unit4A = nil // Apartment is not deallocated

To resolve the cycle, use weak or unowned references. In this case, tenant in Apartment should be a weak reference:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Using weak reference

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) is being initialized")
    }

    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil // Person is deallocated
unit4A = nil // Apartment is deallocated

Choosing between weak and unowned depends on the relationship between objects:

  • Weak: If one object can become nil before the other is deallocated (optional value).
  • Unowned: If one object always exists as long as the other does (non-optional).

ARC also manages memory used by closures that capture class instances. Strong reference cycles can occur when a closure captures an instance of a class, and that instance also has a strong reference to the closure. Capture lists in closures are used to prevent this.

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        // No capture list, potential strong cycle
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

paragraph = nil // HTMLElement is not deallocated

Solution using capture list [weak self]:

class AnHTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in // Using weak self
        guard let self = self else { return "" } // Check if self exists
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

paragraph = nil // AnHTMLElement is deallocated

Using [unowned self] is possible if you are sure that the class instance (self) will not become nil before the closure executes.

ARC greatly simplifies memory management in Swift compared to manual management, but understanding reference types is essential to prevent memory leaks.

How does automatic memory management (ARC) work in… - sobes.tech