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 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.
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.
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.
// 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 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.
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.
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.
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 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.
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 (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 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.
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.
| Werkzeug | Typ | Einsatzzeitpunkt |
|---|---|---|
| Memory Debugger | Visueller Graph | Manuelle Prüfung nach Navigation |
| Instruments Leaks | Automatische Analyse | Regressionstests, CI |
| deinit print | Manuelle Protokollierung | Entwicklung, Code-Review |
| Malloc Scribble | Laufzeit-Flag | Debugging 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 Cycles zu verhindern ist einfacher, als sie in der Produktion zu beheben. Hier sind einige Regeln, die das Risiko zyklischer Referenzen minimieren.
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.
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.
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.
// 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
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.
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.
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.
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.
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
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.
Lesen Sie auch