Sobes.tech
Middle

Wie funktioniert die automatische Speicherverwaltung (ARC) in Swift?

sobes.tech KI

Antwort von AI

ARC (Automatischer Referenzzähler) in Swift verfolgt und verwaltet die Speichernutzung Ihrer Anwendungen. Es gibt den von Klasseninstanzen genutzten Speicher automatisch frei, wenn diese nicht mehr benötigt werden, um Speicherlecks zu verhindern.

Beim Erstellen einer neuen Instanz einer Klasse weist ARC einen Speicherblock zu, um Informationen über diese Instanz zu speichern. Wenn die Instanz nicht mehr benötigt wird, gibt ARC diesen Speicher frei, damit er für andere Zwecke verwendet werden kann.

ARC funktioniert, indem es die Anzahl der starken Referenzen (strong references) auf jede Klasseninstanz zählt. Eine starke Referenz hält die Instanz im Speicher. Wenn die Anzahl der starken Referenzen auf null sinkt, gibt ARC den zugehörigen Speicher frei.

Es gibt drei Arten von Referenzen in Swift, die ARC beeinflussen:

  1. Starke Referenzen (Strong References): Erhöhen den Referenzzähler der Instanz.
  2. Schwache Referenzen (Weak References): Erhöhen den Referenzzähler nicht. Werden verwendet, um zyklische starke Referenzen zu vermeiden, wenn zwei Klasseninstanzen sich gegenseitig mit starken Referenzen halten. Die schwache Referenz wird automatisch nil, wenn das referenzierte Objekt deinitialisiert wird.
  3. Unowned Referenzen (Unowned References): Erhöhen ebenfalls nicht den Referenzzähler. Werden verwendet, wenn Sie sicher sind, dass die referenzierte Instanz immer existiert, solange die aktuelle Instanz existiert. Können nicht optional sein (nil). Der Zugriff auf ein deinitialisiertes Objekt über eine unowned Referenz führt zu einem Laufzeitfehler.

Beispiel für einen Zyklus starker Referenzen:

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

    init(name: String) {
        self.name = name
        print("\(name) wird initialisiert")
    }

    deinit {
        print("\(name) wird deinitialisiert")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) wird initialisiert")
    }

    deinit {
        print("Apartment \(unit) wird deinitialisiert")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person wird nicht deinitialisiert
unit4A = nil // Apartment wird nicht deinitialisiert

Um das Problem des Zyklus zu lösen, verwenden Sie schwache oder unowned Referenzen. In diesem Fall sollte tenant in Apartment eine schwache Referenz sein:

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

    init(name: String) {
        self.name = name
        print("\(name) wird initialisiert")
    }

    deinit {
        print("\(name) wird deinitialisiert")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Verwendung einer schwachen Referenz

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) wird initialisiert")
    }

    deinit {
        print("Apartment \(unit) wird deinitialisiert")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person wird deinitialisiert
unit4A = nil // Apartment wird deinitialisiert

Die Wahl zwischen schwacher und unowned Referenz hängt von der Beziehung zwischen den Objekten ab:

  • Weak: Wenn eines der Objekte nil werden kann, bevor das andere deinitialisiert wird (optionaler Wert).
  • Unowned: Wenn eines der Objekte immer existiert, solange das andere existiert (nicht optionaler Wert).

ARC verwaltet auch den Speicher, der von Closures verwendet wird, die Instanzen von Klassen erfassen. Es können zyklische starke Referenzen entstehen, wenn ein Closure eine Klasseninstanz erfasst und diese Instanz eine starke Referenz auf das Closure hat. Um dies zu verhindern, werden Capture-Listen in Closures verwendet.

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

    lazy var asHTML: () -> String = {
        // Keine Capture-Liste, mögliches starker Zyklus
        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) wird deinitialisiert")
    }
}

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

paragraph = nil // HTMLElement wird nicht deinitialisiert

Lösung mit Capture-Liste [weak self]:

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

    lazy var asHTML: () -> String = { [weak self] in // Verwendung von weak self
        guard let self = self else { return "" } // Überprüfung auf Existenz von self
        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) wird deinitialisiert")
    }
}

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

paragraph = nil // AnHTMLElement wird deinitialisiert

Die Verwendung von [unowned self] ist möglich, wenn Sie sicher sind, dass die Instanz der Klasse (self) nicht nil wird, bevor die Closure ausgeführt wird.

ARC vereinfacht die Speicherverwaltung in Swift erheblich im Vergleich zur manuellen Verwaltung, erfordert jedoch ein Verständnis der Referenztypen, um Speicherlecks zu vermeiden.