Warum verwendet Swift keinen traditionellen Garbage Collector?
\nWenn Sie aus der Welt von Python oder Java zu Swift kommen, werden Sie sicherlich überrascht sein, dass diese Sprache keinen klassischen Garbage Collector hat. Stattdessen hat Apple den Mechanismus Automatic Reference Counting (ARC) implementiert – die automatische Referenzzählung. Und das ist keine bloße Laune: Auf mobilen Geräten zählt jeder Prozessortakt und jedes Byte Speicher. Ein traditioneller GC „friert“ die Anwendung während der Sammlung ein, was für iOS mit seiner Anforderung an flüssige Animationen (60 FPS) eine Katastrophe wäre.
\nIn diesem Artikel werden wir untersuchen, wie die Speicherverwaltung in Swift tatsächlich funktioniert, wo die Fallstricke mit zyklischen Referenzen lauern und wie man Code schreibt, der nicht „ausläuft“.
\n\nARC: Wie funktioniert es unter der Haube?
\nARC ist kein Garbage Collector im herkömmlichen Sinne. Es arbeitet zur Kompilierungszeit: Der Compiler fügt an den entsprechenden Stellen retain- und release-Anweisungen ein. Aber es gibt einen Haken: ARC kann keine zyklischen Referenzen (retain cycles) verarbeiten. Wenn zwei Objekte sich gegenseitig mit starken Referenzen halten, wird ihr Referenzzähler nie auf Null fallen – der Speicher leckt.
Beispiel: Eine einfache Klasse und Referenzzählung
\nclass Person {\n let name: String\n init(name: String) {\n self.name = name\n print("\\(name) erschien im Speicher")\n }\n deinit {\n print("\\(name) verschwand aus dem Speicher")\n }\n}\n\nvar person1: Person? = Person(name: "Alice") // retain count = 1\nvar person2 = person1 // retain count = 2\nperson1 = nil // retain count = 1\nperson2 = nil // retain count = 0 → deinit\n\nIn der Konsole sehen Sie: „Alice erschien im Speicher“ und nach einiger Zeit – „Alice verschwand aus dem Speicher“. Alles korrekt.
\n\nZyklische Referenzen: Der größte Feind des Speichers
\nStellen Sie sich vor: Sie haben einen User und ein Account. Der Benutzer hat ein Konto, und das Konto kennt seinen Besitzer. Wenn beide Eigenschaften starke Referenzen sind, entsteht ein geschlossener Kreislauf:
class User {\n let name: String\n var account: Account?\n init(name: String) { self.name = name }\n deinit { print("User \\(name) gelöscht") }\n}\n\nclass Account {\n let number: String\n var owner: User?\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) gelöscht") }\n}\n\nvar user: User? = User(name: "Boris")\nvar account: Account? = Account(number: "12345")\nuser?.account = account\naccount?.owner = user\n\nuser = nil // User wird nicht gelöscht! account?.owner hält immer noch eine Referenz\naccount = nil // Account wird nicht gelöscht! user?.account hält eine Referenz\n// Speicherleck!\n\nIn der Konsole erscheint keine einzige deinit-Nachricht. Die Objekte hängen für immer im Speicher fest. Dies ist ein retain cycle – ein klassisches Problem der Speicherverwaltung in Swift.
Wie behandelt man es? Weak und unowned
\nDie Lösung besteht darin, eine der Referenzen schwach (weak) oder herrenlos (unowned) zu machen. Der Unterschied:
- \n
- weak – immer optional, wird automatisch
nil, wenn das Objekt gelöscht wird. Sicher. \n - unowned – nicht optional, geht davon aus, dass das Objekt länger lebt. Bei Zugriff auf ein gelöschtes Objekt – Absturz. \n
class Account {\n let number: String\n weak var owner: User? // ← schwache Referenz\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) gelöscht") }\n}\n// Jetzt gibt user = nil den User frei, und account?.owner wird nil\n\n\nGarbage Collector vs ARC: Was ist schneller?
\nSeien wir ehrlich: ARC ist kein „Garbage Collector“ im klassischen Sinne. Ein echter GC (wie in Java, Go oder Python) arbeitet zur Laufzeit: Er scannt periodisch den Heap, findet unerreichbare Objekte und gibt sie frei. Dies erfordert Pausen (stop-the-world).
\nARC hingegen ist eine stat