Sobes.tech
Middle+

Erzählen Sie mir vom Optimierungsmechanismus für Kopieren in iOS.

sobes.tech KI

Antwort von AI

Der Optimierungsmechanismus für Kopien in iOS und macOS, der häufig mit der Verwendung der Struktur Copy-on-Write (CoW) oder der Wertsemantik verbunden ist, ermöglicht es, unnötige Datenkopien beim Übergang von Strukturen oder Klassen mit Wertsemantik wie Arrays, Wörterbüchern, Strings und sogar benutzerdefinierten Strukturen zu vermeiden, sofern sie die Protokolle Equatable und Hashable implementieren (nicht strikt, aber in typischen Fällen).

Das Wesentliche von CoW:

  1. Beim Erstellen einer Kopie einer Struktur (z.B. Zuweisung einer Variablen an eine andere, Übergabe an eine Funktion per Wert) erfolgt keine tatsächliche Datenkopie. Beide Variablen verweisen auf dieselben Daten im Speicher. Der Referenzzähler der Daten erhöht sich.
  2. Die Kopie wird aufgeschoben, bis eine der Kopien versucht, die Daten zu ändern.
  3. Erst beim Ändern werden die Daten tatsächlich kopiert, und die Änderung erfolgt an der neuen Kopie. Die ursprüngliche Kopie verweist weiterhin auf die ursprünglichen Daten (sofern noch mindestens ein Besitzer vorhanden ist).

Vorteile:

  • Reduzierung der Kopierkosten, insbesondere bei großen Datenmengen (Arrays, Strings).
  • Verringerung des Speicherverbrauchs, da mehrere Entitäten dieselben Daten gemeinsam nutzen können.
  • Leistungssteigerung beim Übergang von Strukturen mit Wertsemantik.

Beispiele, in denen CoW aktiv verwendet wird:

  • NSArray, NSDictionary, NSString: In Objective-C verwenden diese Klassen CoW zur Optimierung.
  • Array, Dictionary, String in Swift: Diese Basistypen in Swift haben Wertsemantik und verwenden CoW.
var array1 = [1, 2, 3]
var array2 = array1 // Es findet keine Datenkopie statt, beide Variablen verweisen auf dieselben Daten

array2.append(4) // Es erfolgt eine echte Kopie der Daten für array2, dann append
// Jetzt ist array1 = [1, 2, 3] und array2 = [1, 2, 3, 4]

Nachteile:

  • Kleine zusätzliche Kosten für die Überprüfung, ob eine Kopie notwendig ist, bei jeder Änderung.
  • Möglichkeit versteckter Kopien, die die Leistung an unerwarteten Stellen beeinflussen können.

Es ist wichtig zu beachten, dass CoW effizient für Strukturen mit Wertsemantik (struct, enum) funktioniert. Klassen haben Referenzsemantik, und bei Zuweisung oder Übergabe einer Klasseninstanz wird nur die Referenz kopiert, nicht die Daten des Objekts.

In Swift arbeiten die meisten eingebauten Sammlungen und Strings mit Wertsemantik und verwenden CoW im Hintergrund. Beim Erstellen eigener Strukturen, die referenzielle Typen enthalten (z.B. Klassen), wird CoW auf die Struktur selbst angewendet, aber nicht auf den Inhalt des referenziellen Typs. Änderungen an einer referenziellen Eigenschaft innerhalb einer Struktur lösen kein CoW für die gesamte Struktur aus, können aber CoW für den referenziellen Typ erfordern, wenn dieser es unterstützt.

Um CoW manuell in eigenen Swift-Datentypen zu implementieren, kann man eine Kombination aus einem referenziellen Typ zur Speicherung der Daten (z.B. eine Klasse mit einer internen Struktur) und einer Überprüfung der Einzigartigkeit der Referenz vor der Änderung verwenden (isKnownUniquelyReferenced).

class Container<T> {
    var value: T

    init(_ value: T) {
        self.value = value
    }
}

struct CustomStruct {
    private var _container: Container<[Int]>

    var data: [Int] {
        get { return _container.value }
        set {
            // Überprüfen, ob der Container eindeutig referenziert wird
            // Falls nicht, eine neue Kopie des Containers und der Daten erstellen
            if !isKnownUniquelyReferenced(&_container) {
                _container = Container(newValue)
            } else {
                // Falls ja, Daten direkt im aktuellen Container ändern
                _container.value = newValue
            }
        }
    }

    init(data: [Int]) {
        _container = Container(data)
    }
}

var s1 = CustomStruct(data: [1, 2, 3])
var s2 = s1 // CoW: _container in s1 und s2 zeigen jetzt auf dasselbe Container-Exemplar

s2.data.append(4) // Setter für data wird aufgerufen.
                   // isKnownUniquelyReferenced für _container in s2 ist false (weil s1 auch darauf verweist).
                   // Es wird eine neue Kopie des Containers mit [1, 2, 3, 4] erstellt, s2._container zeigt darauf.
                   // s1._container zeigt immer noch auf den alten Container mit [1, 2, 3].

print(s1.data) // Ausgabe: [1, 2, 3]
print(s2.data) // Ausgabe: [1, 2, 3, 4]