Retain Cycle — Wesen, Ursachen und Beseitigung in der App-Entwicklung

Autor: IT Sectr Veröffentlicht: 2026-03-29 Lesezeit: 8 Min.

Ein Retain Cycle ist eine Situation in ARC, in der zwei oder mehr Objekte einander durch starke Referenzen referenzieren und einen geschlossenen Kreis bilden. Laut Apple Memory Management Guide, 2026 blockiert ein Retain Cycle die Freigabe aller Objekte im Zyklus, da jedes einen Retain Count von ≥ 1 hat. Im Gegensatz zu einem Speicherleck in GC garantiert ein Retain Cycle, dass Objekte lebendig bleiben, solange mindestens ein externer Teilnehmer des Zyklus lebt — und selbst nach dem Verlust aller externen Referenzen, wenn der Zyklus isoliert ist.

Wichtige Erkenntnisse

  • Retain Cycle — eine geschlossene Kette starker Referenzen, bei der Objekte nicht von ARC freigegeben werden können
  • Ursache — zwei (oder mehr) Objekte halten starke Referenzen aufeinander, Nullsetzen des Retain Count unmöglich
  • Folge — Speicherleck: Objekte bleiben für immer im Speicher, RAM-Verbrauch steigt
  • Lösung — Ersetzen einer der starken Referenzen im Zyklus durch weak oder unowned
  • Diagnose — Xcode Memory Debugger, Instruments Leaks, Debug Memory Graph

Was ist Retain Cycle?

Retain Cycle ist eine Situation, in der zwei oder mehr Objekte einander durch starke Referenzen besitzen und einen geschlossenen Abhängigkeitsgraphen erzeugen. ARC kann keines dieser Objekte freigeben, da der Retain Count jedes Objekts immer ≥ 1 ist: Objekt A hält B, B hält A, und ihre Zähler werden nie Null.

Das Problem tritt ausschließlich in Referenzzählsystemen (ARC, MRR) auf. Bei der Garbage Collection bestimmt der Sammler die Unerreichbarkeit durch den Referenzgraphen aus der Wurzelmenge — Zyklen sind kein Hindernis. In ARC jedoch ist ein Zyklus gleichbedeutend mit einem Leck, da deterministische Freigabe durch Zählung zirkuläre Abhängigkeiten nicht auflösen kann.

Laut WWDC 2012 Session 406 ist Retain Cycle die häufigste Ursache für Speicherlecks in Objective-C- und Swift-Anwendungen. Typische Szenarien: Eltern-Kind-Beziehungen mit Delegaten, Closures, die self erfassen, und geschichtete Architekturen mit bidirektionalen Beziehungen.

Retain Cycle Beispiele in der iOS-Entwicklung

Lassen Sie uns klassische Retain-Cycle-Szenarien untersuchen, die jeder iOS-Entwickler antrifft. Das Verständnis dieser Muster ist die Grundlage für das Schreiben von sicherem Code mit ARC.

Parent-Child mit Delegat

Klassisches Szenario: Ein Elternobjekt (z. B. UIViewController) erzeugt ein Kindobjekt und wird dessen Delegat. Wenn beide starke Referenzen verwenden, entsteht ein Retain Cycle. Die Lösung — der Delegat sollte weak sein.

swift
// FEHLER: Retain Cycle durch starken Delegaten
protocol ChildDelegate: AnyObject { }

class ParentVC: UIViewController, ChildDelegate {
    var child: ChildVC?

    func showChild() {
        child = ChildVC()
        child?.delegate = self        // Parent → Child (strong)
    }                                 // Child → Parent (strong über delegate)
}                                     // ⚠️ Retain Cycle!

class ChildVC: UIViewController {
    var delegate: ChildDelegate?    // ❌ strong standardmäßig
}

// KORREKTUR: weak Delegat
class ChildVC: UIViewController {
    weak var delegate: ChildDelegate? // ✅ weak — hält nicht
}

Im Beispiel hält ParentVC eine starke Referenz auf ChildVC über die child-Eigenschaft. ChildVC hält eine starke Referenz auf ParentVC über delegate. Der Zyklus ist geschlossen. Korrektur: weak var delegate — die Referenz erhöht den Retain Count nicht, und ParentVC kann freigegeben werden.

NSTimer und Retain Cycle

NSTimer ist eine klassische Quelle für Retain Cycles. Der Timer hält sein Target (normalerweise self), und das Target hält den Timer über eine Eigenschaft. Selbst wenn der Timer einmalig ist, wird er erst nach invalidate freigegeben. Lösung: immer timer.invalidate() in deinit oder viewDidDisappear aufrufen.

Geschichtete Architekturen

In Architekturen mit kaskadierendem Besitz (Koordinatoren, Router) treten häufig mehrstufige Zyklen auf: Coordinator → ViewController → ViewModel → Coordinator (über Callback). Jede starke Referenz in der Kette muss bewusst gewählt werden — eine weak-Referenz an beliebiger Stelle unterbricht den Zyklus.

Retain Cycle in Swift-Closures

Closures in Swift erfassen externe Variablen durch starke Referenz. Wenn eine Closure als Eigenschaft eines Objekts gespeichert wird (z. B. ein Completion Handler) und self erfasst, entsteht ein Retain Cycle: self → closure → self.

Dies ist die häufigste Quelle für Retain Cycles in der modernen Swift-Entwicklung. Sie tritt implizit auf — ein Entwickler kann die Erfassung von self in einer Closure übersehen, besonders bei Verwendung der Kurzsyntax ohne explizites self.

swift
class DownloadService {
    var onComplete: ((Data) -> Void)?
    var result: Data?

    func startDownload() {
        // ❌ Retain Cycle: self → onComplete → self
        onComplete = { data in
            self.result = data
            self.notifyUI()
        }

        // ✅ Korrektur: Capture-List mit weak self
        onComplete = { [weak self] data in
            guard let self else { return }
            self.result = data
            self.notifyUI()
        }
    }

    func notifyUI() { }
}

Eine Capture-List [weak self] erzeugt eine schwache Referenz auf self innerhalb der Closure. Wenn DownloadService vor der Ausführung der Closure freigegeben wird, wird self zu nil, und der Code verlässt sicher über guard. Dies ist ein Standardmuster für asynchrone Closures in Swift — es sollte immer verwendet werden, wenn eine Closure als Eigenschaft gespeichert wird.

Unowned self in Closures

unowned self ist eine Alternative zu weak self, wenn garantiert ist, dass self länger lebt als die Closure. Beispiel: synchrone Closures, die sofort ausgeführt werden (sorted, filter). In diesen Fällen lebt self definitiv, und unowned ist sicher. Allerdings stürzt unowned ab, wenn auf ein freigegebenes Objekt zugegriffen wird — daher gilt weak als sichere Standardwahl.

Wie man Retain Cycles erkennt: Diagnosewerkzeuge

Die frühzeitige Erkennung von Retain Cycles ist entscheidend für die Anwendungsleistung. Lassen Sie uns die wichtigsten Werkzeuge und Techniken zur Identifizierung zyklischer Referenzen in der iOS-Entwicklung betrachten.

Xcode Memory Debugger

Xcode Memory Debugger (Debug Memory Graph) ist ein visuelles Werkzeug, das den Graphen von Objekten im Speicher mit ihren Referenzen zeigt. Ein Retain Cycle erscheint als geschlossene Kette starker Pfeile. Zum Starten: Klicken Sie während der App-Ausführung auf die Schaltfläche Debug Memory Graph im Bereich Debug area. Jedes Objekt wird mit seinem Typ, seiner Adresse und seiner Referenzliste angezeigt.

Instruments Leaks

Instruments Leaks ist ein Profiler zur automatischen Leckerkennung. Es zeichnet Allokationen auf und analysiert den Referenzgraphen in Echtzeit. Es erkennt nicht nur Retain Cycles, sondern auch vergessene Referenzen, nicht freigegebene ViewController und andere Lecks. Leaks zeigt das genaue Objekt und die Haltekette an.

Deinit-Protokollierung

Die einfachste Methode ist das Hinzufügen eines print in deinit jeder Schlüsselklasse. Wenn deinit nicht aufgerufen wird, wenn das Objekt erwartungsgemà0ß zerstört wird, liegt ein Retain Cycle vor. Diese Methode erfordert keine Werkzeuge und ist für die Erstdiagnose effektiv.

WerkzeugTypEinsatzzeitpunkt
Memory DebuggerVisueller GraphManuelle Prüfung nach Navigation
Instruments LeaksAutomatische AnalyseRegressionstests, CI
deinit printManuelle ProtokollierungEntwicklung, Code-Review
Malloc ScribbleLaufzeit-FlagDebugging von Use-after-free

Empfohlener Ansatz: Verwenden Sie deinit-Protokollierung während der Entwicklung, Memory Debugger bei manuellen Tests und Instruments Leaks in der CI/CD-Pipeline zur automatischen Regressionsleckerkennung.

Retain Cycle Prävention und Best Practices

Retain Cycles zu verhindern ist einfacher, als sie in der Produktion zu beheben. Hier sind einige Regeln, die das Risiko zyklischer Referenzen minimieren.

Weak-Delegat-Regel

Alle Delegaten und dataSources sollten weak sein. Diese Regel ist in UIKit eingebaut: Alle Delegatprotokolle im Apple SDK werden mit weak-Eigenschaften deklariert (UITableView.delegate, UICollectionView.dataSource). Für Ihre eigenen Protokolle verwenden Sie weak var delegate: MyDelegate? und leiten Sie das Protokoll von AnyObject ab.

Capture-List in Closures

Jede Closure, die als Eigenschaft gespeichert wird (Completion Handler, Callback) und self erfasst, muss [weak self] in der Capture-List verwenden. Die Ausnahme sind Closures, die sofort ausgeführt und nicht gespeichert werden (sorted, map, filter). Für diese ist unowned self sicher.

Architekturprüfung

In komplexen Architekturen (VIPER, Coordinators, Redux) verfolgen Sie die Richtung der starken Referenzen. Der Besitzer hält eine starke Referenz auf den Untergebenen, aber der Untergebene sollte den Besitzer nur durch weak oder unowned referenzieren. Unidirektionaler Datenfluss vereinfacht das Referenzmanagement.

swift
// Beispiel: Prüfung mit deinit-Protokollierung
class BaseViewController: UIViewController {
    deinit {
        print("✅ \(type(of: self)) deallocated")
    }
}

// Verwendung: alle ViewController erben von BaseViewController
class ProfileVC: BaseViewController {
    var viewModel: ProfileViewModel?
    var onLogout: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()
        onLogout = { [weak self] in
            self?.dismiss(animated: true)
        }
    }
}
// Beim Schließen von ProfileVC erwarten wir "✅ ProfileVC deallocated" in der Konsole

Eine Basisklasse mit deinit-Protokollierung gibt sofortiges Feedback. Wenn die Meldung beim erwarteten Schließen des Bildschirms nicht erscheint, liegt in dieser Klasse ein Retain Cycle vor. Fügen Sie diese Praxis für alle ViewController zur Projektvorlage hinzu.

Häufig gestellte Fragen

Was unterscheidet Retain Cycle von einem Speicherleck in GC?

Retain Cycle ist ein spezifisches ARC-Problem, bei dem ein geschlossener Kreis starker Referenzen die Freigabe blockiert. In GC analysiert der Sammler die Erreichbarkeit aus der Wurzelmenge, nicht Referenzzähler — daher sind Zyklen keine Lecks. In ARC dagegen ist jeder isolierte Zyklus ein garantiertes Leck.

Wie bricht eine weak-Referenz einen Retain Cycle?

Eine weak-Referenz erhöht den Retain Count eines Objekts nicht. Wenn Sie eine der starken Referenzen in einem Zyklus durch weak ersetzen, kann der Retain Count jedes Objekts auf Null fallen. Nach der Freigabe des Objekts wird die weak-Referenz automatisch auf nil gesetzt, was den Zugriff auf freigegebenen Speicher verhindert.

Kann ein Retain Cycle aus drei oder mehr Objekten bestehen?

Ja, ein Retain Cycle kann beliebig viele Objekte umfassen: A → B → C → A. Zum Unterbrechen müssen Sie nur ein Glied im Zyklus durchtrennen — ersetzen Sie eine beliebige starke Referenz durch weak oder unowned. Die Werkzeuge zeigen den gesamten Graphen, nicht nur Objektpaare.

Warum erzeugt GCD DispatchWorkItem keinen Retain Cycle?

GCD (Grand Central Dispatch) speichert die Closure nach der Ausführung nicht. Der DispatchWorkItem wird ausgeführt und freigegeben, selbst wenn die Closure self erfasst. Ein Retain Cycle entsteht nur, wenn eine Closure als Eigenschaft gespeichert wird (Completion Handler in einer Klasse), nicht wenn sie an eine Warteschlange übergeben wird.

Welche Retain-Cycle-Typen werden von Instruments nicht erkannt?

Instruments Leaks findet nicht immer temporäre Retain Cycles (die Sekunden andauern) oder zyklische Referenzen in C/C++-Objekten durch Bridging. Für eine vollständige Überprüfung verwenden Sie Memory Debugger manuell zusammen mit der deinit-Protokollierung aller Schlüsselobjekte in der Szene.

Zusammenfassung

  • Retain Cycle — geschlossene Kette starker Referenzen, die die Objektfreigabe in ARC blockiert
  • Ursachen — Delegaten mit starker Referenz, Closures mit self-Erfassung, bidirektionale Eltern-Kind-Beziehungen
  • Lösung — Ersetzen einer starken Referenz durch weak oder unowned unterbricht den Zyklus
  • Closures — gespeicherte Completion Handler müssen immer [weak self] verwenden
  • Delegaten — immer weak; das Delegatprotokoll muss von AnyObject erben
  • Erkennung — Xcode Memory Debugger, Instruments Leaks, deinit-Protokollierung
  • Prävention — unidirektionaler Datenfluss, weak-Delegaten, Capture-Listen, Basisklasse mit deinit

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch