Sobes.tech
Junior

How does garbage collection work in iOS?

sobes.tech AI

Answer from AI

iOS does not have a traditional garbage collector as in Java or C#. Instead, it uses an automatic memory management mechanism based on reference counting.

Historically, Objective-C used manual memory management (Manual Reference Counting - MRC), then Automatic Reference Counting (ARC) was introduced. Swift uses only ARC.

ARC (Automatic Reference Counting)

ARC is a compiler mechanism that automatically inserts calls to memory management methods such as retain, release, and autorelease (in Objective-C) or similar operations in Swift.

Key principles of ARC:

  1. Reference count: Each object has a reference count indicating how many other objects refer to it.
  2. Incrementing the count: When a strong reference (strong in Swift, retain by default in Objective-C) is created to an object, the reference count increases by one.
  3. Decrementing the count: When a strong reference is removed (e.g., a variable goes out of scope or is assigned nil), the reference count decreases by one.
  4. Memory release: When the reference count of an object reaches zero, ARC automatically deallocates the memory occupied by that object by calling its deinit method (in Swift) or dealloc (in Objective-C).

Types of references in Swift:

  • strong: Makes the object the owner of the reference, increasing the reference count. It is the default reference type.
  • weak: Does not make the object the owner, does not increase the reference count. Used to prevent strong reference cycles. When the object is deallocated, the weak reference is automatically set to nil. Used with optional types.
  • unowned: Similar to weak, but not set to nil upon deallocation. It requires that the object exists for the lifetime of the reference. Not an optional type. Used when it is guaranteed that the reference will always point to a valid object.

Strong reference cycles:

The main problem with ARC is strong reference cycles (retain cycles). They occur when two or more objects hold strong references to each other, preventing their deallocation because their reference counts never reach zero.

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")

// Strong references to each other
john!.apartment = unit4A
unit4A!.tenant = john

// Setting variables to nil does not deallocate memory
john = nil
unit4A = nil // Neither deinit will be called

Resolving strong reference cycles:

To prevent strong reference cycles, weak or unowned references are used.

In this example, Person should have a strong reference to Apartment, and Apartment should have a weak reference to Person (since a person can move out, but the apartment remains).

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? // 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

// Setting variables to nil successfully deallocates memory
john = nil // Person deinitialized
unit4A = nil // Apartment deinitialized

Closures and strong reference cycles:

Strong reference cycles can also occur between class instances and closures if the closure captures a strong reference to the instance, and the instance holds a strong reference to the closure.

Solution: use capture lists with weak or unowned inside closures.

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

    lazy var asHTML: () -> String = {
        [unowned self] in // Capture list with unowned
        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 deinitialized

In conclusion, iOS manages object memory using ARC, based on reference counting, not a traditional garbage collector. Developers should be aware of strong reference cycles and use weak or unowned references to prevent them.

How does garbage collection work in iOS? — iOS - sobes.tech