Sobes.tech
Middle

Was sind Retain Cycles in iOS?

sobes.tech KI

Antwort von AI

Retain-Cycles treten auf, wenn zwei oder mehr Objekte starke Referenzen aufeinander haben, wodurch ein "geschlossener Kreis" entsteht. Infolgedessen erreicht der Referenzzähler jedes Objekts niemals null und es kann nicht durch ARC (Automatisches Referenzzählen) freigegeben werden. Dies führt zu Speicherlecks, da die Objekte im Speicher verbleiben, auch nachdem sie nicht mehr verwendet werden.

Beispiel:

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

In diesem Beispiel, wenn Person eine starke Referenz auf Apartment hat, und Apartment eine starke Referenz auf Person, werden diese Objekte beim Versuch, sie freizugeben, nicht gelöscht, da ihre Referenzzähler nicht null erreichen.

Wie man das Problem der Retain-Cycles löst:

Verwenden Sie schwache (weak) oder unowned Referenzen.

  • weak: Wird verwendet, wenn das verknüpfte Objekt möglicherweise nil wird, während das andere Objekt lebt. Die Referenz wird automatisch nil, wenn das referenzierte Objekt freigegeben wird. Es ist optional.
  • unowned: Wird verwendet, wenn bekannt ist, dass das verknüpfte Objekt die gleiche oder eine längere Lebensdauer hat als das Objekt mit der Referenz. Es kann nicht nil sein.

Beispiel mit weak:

Ändern wir die Referenz tenant in der Klasse Apartment zu weak:

class Apartment {
    let unit: String
    weak var tenant: Person? // Verwendung von weak

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

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

Wenn das Person-Objekt freigegeben wird, wird die Referenz tenant in Apartment automatisch nil, wodurch der Retain-Cycle gebrochen wird und Apartment ebenfalls freigegeben werden kann.

Vergleichstabelle weak und unowned:

Eigenschaft weak unowned
Referenztyp Optional Nicht optional
Kann nil sein? Ja, bei Freigabe des Objekts Nein
Wann verwenden Wenn das verknüpfte Objekt nil werden kann Wenn bekannt ist, dass das Objekt nicht nil wird
Leistung Etwas langsamer wegen nil-Prüfung Etwas schneller

Außerdem können Retain-Cycles auch in Closures auftreten.

Wenn ein Closure Eigenschaften einer Instanz oder self erfasst, kann es eine starke Referenz auf das Objekt erstellen. Wenn dieses Objekt ebenfalls eine starke Referenz auf das Closure hat (z.B. durch eine Closure-Eigenschaft oder einen Delegate), entsteht ein Cycle.

Wie man Retain-Cycles in Closures vermeidet:

Verwenden Sie Capture List.

class SomeClass {
    var value = 0

    func doSomethingDelayed() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // Capture list mit weak self
            guard let self = self else { return } // `self` auf `nil` prüfen
            self.value += 1
            print("Wert ist jetzt \(self.value)")
        }
    }
}

In diesem Beispiel erstellt [weak self] eine schwache Referenz auf self im Closure, um einen Retain-Cycle zu vermeiden.