Hintergrund:
Mit der Entwicklung von Objective-C und der Einführung von Swift stellte sich Apple die Frage nach einem automatischen und sicheren Management des Speichers von Anwendungen, ohne dass Programmierer explizit mit den Befehlen retain/release arbeiten müssen. Swift verwendet Automatic Reference Counting (ARC), das die Anzahl der Referenzen auf Klassenobjekte verwaltet und automatisch den Speicher freigibt, wenn der letzte Besitzer verschwindet.
Problem:
Da Swift eine multiparadigmatische Sprache ist, arbeitet es sowohl mit Value-Typen (struct, enum) als auch mit Referenztypen (class). Für letztere ist es wichtig, dass der Speicher rechtzeitig freigegeben wird, da sonst ein Speicherleck entstehen kann. Eine komplexe Situation tritt bei einem Retain Cycle (zyklische Referenzen) auf, wenn zwei Objekte sich gegenseitig referenzieren und ARC nicht in der Lage ist, den Speicher freizugeben.
Lösung:
Swift wendet ARC nur auf Klassenobjekte an. Wenn die Anzahl der Referenzen 0 beträgt, wird der Speicher freigegeben. Zur Lösung des Problems des Retain Cycle werden die Schlüsselwörter weak/unowned verwendet.
Beispielcode:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) wird deinitialisiert") } } class Pet { var owner: Person? init() {} deinit { print("Haustier wird deinitialisiert") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // Beide Objekte werden NICHT freigegeben, ein Retain Cycle entsteht
Wichtige Merkmale:
Kann man ARC auf Value-Typen anwenden?
Nein, ARC verfolgt nur Referenzen auf Klassenobjekte. Strukturen und Enums werden per Wert übergeben, und Swift gibt ihren Speicher automatisch frei (normalerweise beim Verlassen des Gültigkeitsbereichs).
Was passiert, wenn man weak/unowned in den Eigenschaften von zwei sich gegenseitig referenzierenden Objekten nicht verwendet?
Es entsteht eine zirkuläre Referenz (Retain Cycle), was dazu führt, dass ARC den Speicher für diese Objekte niemals freigibt. Ein solcher Code führt zu einem Speicherleck — verwenden Sie immer weak (oder unowned) für mindestens eine Seite dieser Verbindung.
Wofür ist unowned gut, wenn es weak gibt?
unowned ist in Fällen erforderlich, in denen die Referenz während der Lebensdauer eines anderen Objekts immer vorhanden sein muss und niemals nil werden sollte. weak ist eine optionale Referenz, unowned ist nicht optional, jedoch ohne Erhöhung des Retain-Counters.
Beispielcode:
class A { var b: B? } class B { unowned var a: A // niemals nil während der Lebensdauer von B }
Zwei Controller halten starke Referenzen zueinander: einer über eine Delegate-Eigenschaft, der andere über ein Array von untergeordneten Controllern. Der Ersteller verwendet bei dem Delegaten kein weak.
Vorteile: Fehler sind nicht sofort sichtbar, alles "funktioniert" auf den ersten Blick.
Nachteile: Mit dem Wachstum der Anwendung wird der Speicher nicht freigegeben, es entstehen Lecks; bei begrenztem Speicher kann es zu Abstürzen kommen.
Der Delegat und der Ereignisbeobachter werden als weak konfiguriert, das Array von Controllern wird entfernt, wenn die Controller verschwinden. Alles ist sorgfältig dokumentiert.
Vorteile: Es gibt keine Lecks, alle Objekte werden rechtzeitig freigegeben, es gibt keinen Leistungsabfall.
Nachteile: Es kann zu einem unerwarteten Verlust des Delegaten kommen (wenn irgendwo weiter oben eine starke Referenz vergessen wurde); erfordert Aufmerksamkeit bei der Architektur der Anwendung.