Mit der Einführung von Swift 5.5 wurde das Konzept des Ownership-Modells und der Move Semantics in die Sprache integriert, die die Kontrolle über den Besitz von Daten verstärken und dem Compiler ermöglichen, Bewegungen zu optimieren, wodurch die Anzahl der Kopien verringert wird, was in leistungsstarken Szenarien relevant ist.
Move Semantics bedeutet, dass man bei der Übergabe eines Wertes (z.B. struct) den Besitz dieses Wertes "übertragen" kann, ohne es zu kopieren. Dabei kann der Compiler die ursprüngliche Variable ungültig machen (ähnlich wie move in C++). Aktuell werden Ownership-Modell und Move Semantics mehr als Experiment (actor isolation, sendable types, @_move, consuming/self-consuming) umgesetzt und versprechen, im öffentlichen API verfügbar zu werden.
Der Hauptunterschied zu ARC besteht darin, dass Move Semantics auf Wertetypen anwendbar ist, während ARC die Lebensdauer von Referenzobjekten verwaltet.
Beispiel (Besitzsemantik, Swift 5.5+):
func consume<T>(_ x: __owned T) { /* ... */ } struct LargeArray { var storage: [Int] mutating func clear() { storage.removeAll() } consuming func consumeSelf() { // self ist nach dem Aufruf nicht verfügbar } }
Die Verwaltung des Besitzes ermöglicht es, unerwartete Kopien bei der Arbeit mit großen Strukturen zu vermeiden.
Nuancen:
Was ist der Unterschied zwischen der Übergabe eines Strukturobjekts an eine Funktion nach Wert, nach Referenz und nach Move Semantics in Swift?
Antwort:
Beispiel:
func foo(_ x: MyStruct) { /* Kopie */ } func bar(_ x: inout MyStruct) { /* nach Referenz */ } func baz(_ x: __owned MyStruct) { /* Move Semantics, keine Kopie */ }
Geschichte
Im Projekt kam es beim Übertragen großer Strukturen über Funktionen immer zu impliziten Kopien, die den Speicherbedarf erhöhten. Nach der Einführung experimenteller Move Semantics und einer durchdachteren Verwaltung des Besitzes konnte die Last sauber umverteilt und kritische Bereiche beschleunigt werden.
Geschichte
Viele Entwickler verwendeten inout falsch und dachten, es implementiere Move, während der Wert weiterhin verfügbar blieb, was zu mehrdeutigen Besitztümern der Variablen führte, was Bugs und Logikfehler zur Folge hatte.
Geschichte
Fehler beim Datenmanagement zwischen Threads: Fehlen des Sendable-Qualifikators bei Strukturen, was zu unerwarteten Kopien oder Besitzfehlern bei asynchroner Arbeit mit großen Strukturen über Actor oder Task führte.