Deep Copy vs. Shallow Copy - und wie Sie sie in Swift verwenden können

Das Kopieren eines Objekts war schon immer ein wesentlicher Bestandteil des Codierungsparadigmas. Sei es in Swift, Objective-C, JAVA oder einer anderen Sprache, wir müssen immer ein Objekt für die Verwendung in verschiedenen Kontexten kopieren.

In diesem Artikel werden wir detailliert erläutern, wie Sie verschiedene Datentypen in Swift kopieren und wie sie sich unter verschiedenen Umständen verhalten.

Wert- und Referenztypen

Alle Datentypen in Swift lassen sich grob in zwei Kategorien einteilen, nämlich Werttypen und Referenztypen.

  • Werttyp - Jede Instanz behält eine eindeutige Kopie ihrer Daten. Datentypen, die in diese Kategorie fallen, umfassen - alle grundlegenden Datentypen, struct, enum, array, tuples.
  • Referenztyp - Instanzen nutzen eine einzelne Kopie der Daten gemeinsam, und der Typ wird normalerweise als Klasse definiert.

Das unterscheidendste Merkmal beider Typen liegt in ihrem Kopierverhalten.

Was ist Deep and Shallow-Kopie?

Eine Instanz, unabhängig davon, ob es sich um einen Werttyp oder einen Referenztyp handelt, kann auf eine der folgenden Arten kopiert werden:

Deep Copy - Kopiert alles

  • Bei einer tiefen Kopie wird jedes Objekt, auf das die Quelle zeigt, kopiert und auf das die Kopie vom Ziel zeigt. Es werden also zwei völlig getrennte Objekte erstellt.
  • Sammlungen - Eine umfangreiche Kopie einer Sammlung besteht aus zwei Sammlungen, bei denen alle Elemente der ursprünglichen Sammlung dupliziert sind.
  • Weniger anfällig für Rennbedingungen und gute Leistung in einer Umgebung mit mehreren Threads - Änderungen an einem Objekt haben keine Auswirkungen auf ein anderes Objekt.
  • Werttypen werden tief kopiert.

Im obigen Code

  • Zeile 1: arr1 - Array (ein Wertetyp) von Strings
  • Zeile 2: arr1 wird arr2 zugewiesen. Dadurch wird eine tiefe Kopie von arr1 erstellt und diese Kopie dann arr2 zugewiesen
  • Zeilen 7 bis 11: Änderungen in arr2 spiegeln sich nicht in arr1 wider.

Dies ist, was Deep Copy ist - völlig getrennte Instanzen. Das gleiche Konzept funktioniert mit allen Werttypen.

In einigen Szenarien, in denen ein Wertetyp verschachtelte Referenztypen enthält, zeigt Deep Copy ein anderes Verhalten. Das werden wir in den nächsten Abschnitten sehen.

Flache Kopie - Dupliziert so wenig wie möglich

  • Bei einer flachen Kopie wird jedes Objekt, auf das die Quelle zeigt, auch vom Ziel angezeigt. Es wird also nur ein Objekt im Speicher angelegt.
  • Sammlungen - Eine flache Kopie einer Sammlung ist eine Kopie der Sammlungsstruktur, nicht der Elemente. Mit einer flachen Kopie teilen sich nun zwei Sammlungen die einzelnen Elemente.
  • Schneller - nur die Referenz wird kopiert.
  • Durch Kopieren von Referenztypen wird eine flache Kopie erstellt.

Im obigen Code

  • Zeilen 1 bis 8: Adressklassentyp
  • Zeile 10: a1 - eine Instanz des Adresstyps
  • Zeile 11: a1 wird a2 zugewiesen. Dies erstellt eine flache Kopie von a1 und weist diese Kopie dann a2 zu, dh, nur die Referenz wird in a2 kopiert.
  • Zeilen 16 bis 19: Änderungen, die in a2 vorgenommen werden, spiegeln sich sicherlich in a1 wider.

In der obigen Abbildung ist zu sehen, dass sowohl a1 als auch a2 auf dieselbe Speicheradresse verweisen.

Referenztypen gründlich kopieren

Ab sofort wissen wir, dass immer, wenn wir versuchen, einen Referenztyp zu kopieren, nur die Referenz auf das Objekt kopiert wird. Es wird kein neues Objekt erstellt. Was ist, wenn wir ein völlig separates Objekt erstellen möchten?

Mit der copy () -Methode können wir eine tiefe Kopie des Referenztyps erstellen. Nach der Dokumentation,

copy () - Gibt das von copy (mit :) zurückgegebene Objekt zurück.

Dies ist eine praktische Methode für Klassen, die das NSCopying-Protokoll verwenden. Eine Ausnahme wird ausgelöst, wenn keine Implementierung für copy (mit :) vorhanden ist.

Strukturieren wir die in Code Snippet 2 erstellte Adressklasse so, dass sie dem NSCopying-Protokoll entspricht.

Im obigen Code

  • Zeilen 1 bis 14: Der Typ der Adressklasse entspricht NSCopying und implementiert die Methode copy (with :)
  • Zeile 16: a1 - eine Instanz des Adresstyps
  • Zeile 17: a1 wird mit der copy () -Methode a2 zugewiesen. Dies erstellt eine tiefe Kopie von a1 und weist diese Kopie dann a2 zu, dh es wird ein komplett neues Objekt erstellt.
  • Zeilen 22 bis 25: Änderungen in a2 werden in a1 nicht berücksichtigt.

Wie aus der obigen Abbildung hervorgeht, verweisen sowohl a1 als auch a2 auf unterschiedliche Speicherstellen.

Schauen wir uns ein anderes Beispiel an. Dieses Mal werden wir sehen, wie es mit verschachtelten Referenztypen funktioniert - einem Referenztyp, der einen anderen Referenztyp enthält.

Im obigen Code

  • Zeile 22: Eine tiefe Kopie von p1 wird p2 mit der copy () -Methode zugewiesen. Dies impliziert, dass eine Änderung an einer von ihnen keine Auswirkung auf die andere haben darf.
  • Zeilen 27 bis 28: Der Name und die Stadtwerte von p2 werden geändert. Diese dürfen sich nicht in p1 widerspiegeln.
  • Zeile 30: Der Name von p1 ist wie erwartet, aber die Stadt? Es sollte "Mumbai" sein, nicht wahr? Aber wir können das nicht sehen. "Bangalore" war nur für p2 richtig? Ja, genau. «

Deep Copy…! Das haben Sie nicht erwartet. Sie sagten, Sie werden alles kopieren. Und jetzt benimmst du dich so. Warum Oh warum..?! Was mache ich jetzt?

Keine Panik. Sehen wir uns an, was Speicheradressen dazu zu sagen haben.

Aus der obigen Abbildung können wir das ersehen

  • p1 und p2 zeigen erwartungsgemäß auf unterschiedliche Speicherstellen.
  • Ihre Adressvariablen verweisen jedoch weiterhin auf denselben Speicherort. Dies bedeutet, dass auch nach dem Kopieren nur die Referenzen kopiert werden - das heißt natürlich eine flache Kopie.

Bitte beachten Sie: Jedes Mal, wenn wir einen Referenztyp kopieren, wird standardmäßig eine flache Kopie erstellt, bis wir ausdrücklich angeben, dass sie tief kopiert werden soll.

Funkkopie (mit Zone: NSZone? = nil) -> Beliebig
{
    let person = Person (Eigenname, Eigenadresse)
    Person zurückgeben
}

In der oben für die Person-Klasse implementierten Methode haben wir eine neue Instanz erstellt, indem wir die Adresse mit self.address kopiert haben. Dadurch wird nur die Referenz auf das Adressobjekt kopiert. Aus diesem Grund verweisen die Adressen von p1 und p2 auf denselben Ort.

Wenn Sie das Objekt also mit der copy () -Methode kopieren, wird keine wirklich tiefe Kopie des Objekts erstellt.

So duplizieren Sie ein Referenzobjekt vollständig: Der Referenztyp muss zusammen mit allen verschachtelten Referenztypen mit der copy () -Methode kopiert werden.

lass person = Person (self.name, self.address.copy () als? Adresse)

Verwenden Sie den obigen Code in der Funkkopie (mit zone: NSZone? = Nil) -> Jede Methode bringt alles zum Laufen. Das können Sie der folgenden Abbildung entnehmen.

True Deep Copy - Referenz- und Werttypen

Wir haben bereits gesehen, wie wir eine tiefe Kopie der Referenztypen erstellen können. Natürlich können wir das mit allen verschachtelten Referenztypen machen.

Aber was ist mit dem verschachtelten Referenztyp in einem Werttyp, dh einem Array von Objekten, oder einer Referenztypvariablen in einer Struktur oder einem Tupel? Können wir das auch mit copy () lösen? Nein, das können wir eigentlich nicht. Die copy () -Methode erfordert die Implementierung des NSCopying-Protokolls, das nur für NSObject-Unterklassen funktioniert. Werttypen unterstützen keine Vererbung, daher können wir copy () nicht mit ihnen verwenden.

In Zeile 2 wird nur die Struktur von arr1 tief kopiert, aber die darin enthaltenen Adressobjekte sind immer noch flach kopiert. Sie können dies anhand der folgenden Speicherkarte sehen.

Die Elemente in arr1 und arr2 zeigen beide auf die gleichen Speicherstellen. Dies hat denselben Grund: Referenztypen werden standardmäßig flach kopiert.

Durch das Serialisieren und anschließende Deserialisieren eines Objekts wird immer ein brandneues Objekt erstellt. Sie gilt sowohl für Werttypen als auch für Referenztypen.

Hier sind einige APIs, mit denen wir Daten serialisieren und de-serialisieren können:

  1. NSCoding - Ein Protokoll, mit dem ein Objekt zur Archivierung und Verteilung codiert und decodiert werden kann. Es funktioniert nur mit Objekten des Klassentyps, da das Erben von NSObject erforderlich ist.
  2. Codierbar - Machen Sie Ihre Datentypen codierbar und decodierbar, um die Kompatibilität mit externen Darstellungen wie JSON zu gewährleisten. Es funktioniert für beide Werttypen - struct, array, tuple, basic data types sowie reference types - class.

Strukturieren wir die Address-Klasse etwas weiter, um sie an das Codable-Protokoll anzupassen, und entfernen Sie den gesamten NSCopying-Code, den wir zuvor in Code Snippet 3 hinzugefügt haben.

Im obigen Code erstellen die Zeilen 11–13 eine echte tiefe Kopie von arr1. Unten ist die Abbildung dargestellt, die ein klares Bild der Speicherorte gibt.

Beim Schreiben kopieren

Beim Schreiben kopieren ist eine Optimierungstechnik, mit der die Leistung beim Kopieren von Werttypen gesteigert werden kann.

Nehmen wir an, wir kopieren einen einzelnen String oder Int oder einen anderen Werttyp. In diesem Fall treten keine entscheidenden Leistungsprobleme auf. Aber was ist, wenn wir ein Array von Tausenden von Elementen kopieren? Wird es immer noch keine Leistungsprobleme geben? Was ist, wenn wir es nur kopieren und an dieser Kopie keine Änderungen vornehmen? Ist das nicht zusätzlicher Speicher, den wir in diesem Fall nur verschwendet haben?

Hier kommt das Konzept von Copy in Write - beim Kopieren verweist jeder Verweis auf dieselbe Speicheradresse. Nur wenn eine der Referenzen die zugrunde liegenden Daten ändert, kopiert Swift die ursprüngliche Instanz und nimmt die Änderung vor.

Das heißt, ob es sich um eine tiefe oder eine flache Kopie handelt, eine neue Kopie wird erst erstellt, wenn wir eine Änderung an einem der Objekte vornehmen.

Im obigen Code

  • Zeile 2: arr2 wird eine tiefe Kopie von arr1 zugewiesen
  • Zeile 4 und 5: arr1 und arr2 zeigen immer noch auf dieselbe Speicheradresse
  • Zeile 7: Änderungen in arr2
  • Zeilen 9 und 10: arr1 und arr2 zeigen nun auf unterschiedliche Speicherstellen

Jetzt wissen Sie mehr über tiefe und flache Kopien und wie sie sich in unterschiedlichen Szenarien mit unterschiedlichen Datentypen verhalten. Sie können sie mit Ihren eigenen Beispielen ausprobieren und sehen, welche Ergebnisse Sie erzielen.

Weitere Lektüre

Vergessen Sie nicht, meine anderen Artikel zu lesen:

  1. Alles über Codable in Swift 4
  2. Alles, was Sie schon immer über Benachrichtigungen in iOS wissen wollten
  3. Malen Sie es mit GRADIENTS - iOS
  4. Codierung für iOS 11: Drag & Drop in Sammlungen und Tabellen
  5. Alles, was Sie über Today Extensions (Widget) in iOS 10 wissen müssen
  6. UICollectionViewCell Auswahl leicht gemacht .. !!

Sie können gerne Kommentare hinterlassen, falls Sie Fragen haben.