von Payal Gupta

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 wird ausführlich erläutert, wie verschiedene Datentypen in Swift kopiert werden 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 speichert eine eindeutige Kopie ihrer Daten. Zu den Datentypen, die in diese Kategorie fallen, gehören — all the basic data types, struct, enum, array, tuples.
  • Referenztyp – Instanzen teilen sich eine einzelne Kopie der Daten, und der Typ wird normalerweise als class definiert.

Das Unterscheidungsmerkmal beider Typen liegt in ihrem Kopierverhalten.

Was ist tiefe und flache 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 — Dupliziert alles

  • Mit einer Deep Copy wird jedes Objekt, auf das die Quelle zeigt, kopiert und die Kopie vom Ziel. Es werden also zwei völlig separate Objekte erstellt.
  • Sammlungen – Eine tiefe Kopie einer Sammlung besteht aus zwei Sammlungen, wobei alle Elemente der ursprünglichen Sammlung dupliziert werden.
  • Weniger anfällig für Race-Bedingungen und gute Leistung in einer Multithread-Umgebung – Änderungen an einem Objekt haben keine Auswirkungen auf ein anderes Objekt.
  • Werttypen werden tief kopiert.

Im obigen Code,

  • Zeile 1: arr1 – Array (ein Werttyp) von Zeichenfolgen
  • Zeile 2: arr1 ist arr2 zugewiesen. Dadurch wird eine tiefe Kopie von arr1 erstellt und diese Kopie dann den Zeilen 7 bis 11 von arr2
  • zugewiesen: Alle in arr2 vorgenommenen Änderungen spiegeln sich nicht in arr1 wider.

Das ist Deep Copy – völlig getrennte Instanzen. Das gleiche Konzept funktioniert mit allen Werttypen.

In einigen Szenarien, d. h. wenn ein Werttyp verschachtelte Referenztypen enthält, zeigt Deep Copy ein anderes Verhalten. Wir werden das in den kommenden 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 erstellt.
  • 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.
  • Beim Kopieren von Referenztypen wird eine flache Kopie erstellt.

Im obigen Code,

  • Zeilen 1 bis 8: Address Klassentyp
  • Zeile 10: a1 — Eine Instanz von Address Typ
  • Zeile 11: a1 ist a2 zugewiesen. Dadurch wird eine flache Kopie von a1 erstellt und diese Kopie dann a2 zugewiesen, dh nur die Referenz wird in a2 kopiert.
  • Zeilen 16 bis 19: alle Änderungen, die in a2 vorgenommen wurden, werden sich sicherlich in a1 widerspiegeln.

In der obigen Abbildung können wir sehen, dass sowohl a1 als auch a2 auf dieselbe Speicheradresse zeigen.

Referenztypen kopieren >

Ab sofort wissen wir, dass jedes Mal, wenn wir versuchen, einen Referenztyp zu kopieren, nur der Verweis 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 Methode copy() können wir eine tiefe Kopie des Referenztyps erstellen. Gemäß der Dokumentation

copy() – Gibt das von copy(with:) zurückgegebene Objekt zurück.

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

Lassen Sie uns das Address class , das wir in Code Snippet 2 erstellt haben, so umstrukturieren, dass es dem NSCopying -Protokoll entspricht.

Im obigen Code,

  • Zeilen 1 bis 14: Address Klassentyp entspricht NSCopying und implementiert copy(with:) Methode
  • Zeile 16: a1 – eine Instanz von Address Typ
  • Zeile 17: a1 wird a2 mit der Methode copy() zugewiesen. Dadurch wird eine tiefe Kopie von a1 erstellt und diese Kopie dann a2 zugewiesen.
  • Zeilen 22 bis 25: Änderungen in a2 werden nicht in a1 wiedergegeben.

Wie aus der obigen Abbildung hervorgeht, zeigen sowohl a1 als auch a2 auf unterschiedliche Speicherorte.

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 Methode copy() zugewiesen. Dies bedeutet, dass jede Änderung in einem von ihnen keine Auswirkungen auf den anderen haben darf.
  • Zeilen 27 bis 28: p2's name und city Werte werden geändert. Diese dürfen sich nicht in p1 widerspiegeln.
  • Zeile 30: p1's name ist wie erwartet, aber es ist city? Es sollte "Mumbai" sein, nicht wahr? Aber das können wir nicht sehen. "Bangalore" war nur für p2 richtig? Ja … genau.?

Tiefe Kopie…!? Das war nicht von dir zu erwarten. Du sagtest, du kopierst alles. Und jetzt benimmst du dich so. Warum oh warum..?! Was mache ich jetzt? ☠️

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

Aus der obigen Abbildung können wir das sehen

  • p1 und p2 zeigen erwartungsgemäß auf verschiedene Speicherorte.
  • Aber ihre address Variablen zeigen immer noch auf denselben Speicherort. Dies bedeutet, dass selbst nach dem tiefen 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 explizit angeben, dass sie tief kopiert werden soll.

func copy(with zone: NSZone? = nil) -> Any{ let person = Person(self.name, self.address) return person}

In der obigen Methode, die wir zuvor für die Klasse Person implementiert haben, haben wir eine neue Instanz erstellt, indem wir die Adresse mit self.address kopiert haben . Dadurch wird nur der Verweis auf das Adressobjekt kopiert. Dies ist der Grund, warum sowohl p1 als auch p2's address auf dieselbe Position zeigen.

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

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

let person = Person(self.name, self.address.copy() as? Address)

Wenn Sie den obigen Code in der func copy(with zone: NSZone? = nil) -> -Methode verwenden, funktioniert alles. Sie können das aus der folgenden Abbildung sehen.

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 tun.

Aber was ist mit dem verschachtelten Referenztyp in einem Werttyp, dh einem Array von Objekten, oder einer Referenztypvariablen in einer Struktur oder möglicherweise einem Tupel? Können wir das auch mit copy() lösen? Nein, das können wir 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 Address -Objekte werden immer noch flach kopiert. Sie können das aus der folgenden Speicherkarte sehen.

Die Elemente in arr1 und arr2 zeigen beide auf dieselben Speicherorte. Dies hat den gleichen Grund – Referenztypen werden standardmäßig nicht kopiert.

Wenn Sie ein Objekt serialisieren und dann de-serialisieren, wird immer ein brandneues Objekt erstellt. Es ist sowohl für Werttypen als auch für Referenztypen gültig.

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 vom Typ class, da von NSObject geerbt werden muss.
  2. Codable – Machen Sie Ihre Datentypen für die Kompatibilität mit externen Darstellungen wie JSON codier- und decodierbar. Es funktioniert sowohl für Werttypen – struct, array, tuple, basic data types als auch für Referenztypen – class.

Lassen Sie uns die Address -Klasse etwas weiter umstrukturieren, um dem Codable -Protokoll zu entsprechen, und den gesamten NSCopying -Code entfernen, 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, die ein klares Bild der Speicherorte gibt.

Copy on Write

Copy on write ist eine Optimierungstechnik, die die Leistung beim Kopieren von Werttypen verbessert.

Angenommen, 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 verursachen? Was ist, wenn wir es einfach kopieren und keine Änderungen an dieser Kopie vornehmen? Ist der zusätzliche Speicher, den wir verwendet haben, in diesem Fall nicht nur Verschwendung?

Hier kommt das Konzept des Kopierens beim Schreiben — beim Kopieren zeigt jede Referenz 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 tiefe Kopie oder flache Kopie, eine neue Kopie wird erst erstellt, wenn wir eine Änderung an einem der Objekte vornehmen.

Im obigen Code,

  • Zeile 2: Eine tiefe Kopie von arr1 wird arr2
  • Zeilen 4 und 5 zugewiesen: arr1 und arr2 zeigen immer noch auf dieselbe Speicheradresse
  • Zeile 7: Änderungen in arr2
  • Zeilen 9 und 10: arr1 und arr2 zeigen jetzt auf verschiedene Speicherorte

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

Weiterführende Literatur

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. Färben Sie es mit FARBVERLÄUFEN — iOS
  4. Codierung für iOS 11: So ziehen Sie & Drop in Sammlungen & Tabellen
  5. Alles, was Sie über ) in iOS 10
  6. UICollectionViewCell Auswahl leicht gemacht..!!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.