by Payal Gupta

kopiowanie obiektu zawsze było istotną częścią paradygmatu kodowania. Czy to w języku Swift, Objective-C, JAVA czy w jakimkolwiek innym języku, zawsze będziemy musieli skopiować obiekt do użytku w różnych kontekstach.

w tym artykule omówimy szczegółowo, jak kopiować różne typy danych w Swift i jak zachowują się w różnych okolicznościach.

typy wartości i odniesienia

Wszystkie typy danych w języku Swift zasadniczo dzielą się na dwie kategorie, a mianowicie typy wartości i typy odniesienia.

  • Typ wartości — każda instancja zachowuje unikalną kopię swoich danych. Typy danych należące do tej kategorii obejmują — all the basic data types, struct, enum, array, tuples.
  • Typ odniesienia — instancje współdzielą jedną kopię danych, a typ jest zwykle definiowany jako class.

najbardziej wyróżniającą cechą obu typów jest ich kopiowanie.

Co To jest głęboka i płytka Kopia?

instancja, niezależnie od tego, czy jest to typ wartości, czy typ odniesienia, może być skopiowana na jeden z następujących sposobów:

Deep copy — duplikuje wszystko

  • przy głębokiej kopii kopiowany jest dowolny obiekt wskazywany przez źródło, a kopia jest wskazywana przez miejsce docelowe. W ten sposób powstaną dwa zupełnie oddzielne obiekty.
  • Kolekcje-głęboka Kopia kolekcji to dwie kolekcje z powielonymi wszystkimi elementami z oryginalnej kolekcji.
  • mniej podatny na warunki wyścigowe i działa dobrze w środowisku wielowątkowym — zmiany w jednym obiekcie nie będą miały wpływu na inny obiekt.
  • typy wartości są kopiowane głęboko.

w powyższym kodzie,

  • linia 1: arr1 – tablica (typ wartości) łańcuchów
  • linia 2: arr1jest przypisana do arr2. Spowoduje to utworzenie głębokiej kopii arr1, a następnie przypisanie tej kopii do arr2
  • linii od 7 do 11: wszelkie zmiany dokonane w arr2 nie odzwierciedlają się w arr1.

tym właśnie jest deep copy — całkowicie oddzielnymi instancjami. Ta sama koncepcja działa ze wszystkimi typami wartości.

w niektórych scenariuszach, tj. gdy typ wartości zawiera zagnieżdżone typy odniesienia, funkcja deep copy ujawnia inny rodzaj zachowania. Zobaczymy to w kolejnych sekcjach.

Płytka Kopia — duplikuje jak najmniej

  • przy płytkiej kopii każdy obiekt wskazywany przez źródło jest również wskazywany przez cel. Tak więc w pamięci zostanie utworzony tylko jeden obiekt.
  • Kolekcje-płytka Kopia kolekcji jest kopią struktury kolekcji, a nie elementów. Przy płytkiej kopii dwie kolekcje dzielą teraz poszczególne elementy.
  • szybciej-kopiowane jest tylko odniesienie.
  • kopiowanie typów odniesienia tworzy płytką kopię.

w powyższym kodzie,

  • linie od 1 do 8: Address class type
  • Line 10: a1 — instancja Address type
  • Line 11: a1jest przypisana do a2. Spowoduje to utworzenie płytkiej kopii a1 , a następnie przypisanie tej kopii do a2, czyli tylko odniesienie jest kopiowane do a2.
  • linie od 16 do 19: wszelkie zmiany dokonane w a2 z pewnością odzwierciedlą się w a1.

na powyższej ilustracji widzimy, że zarówno a1, jak i a2 wskazują ten sam adres pamięci.

kopiowanie typów referencji głęboko

na razie wiemy, że za każdym razem, gdy próbujemy skopiować Typ odniesienia, kopiowane jest tylko odniesienie do obiektu. Nie jest tworzony żaden nowy obiekt. Co jeśli chcemy stworzyć zupełnie osobny obiekt?

możemy utworzyć głęboką kopię typu odniesienia za pomocą metody copy(). Zgodnie z dokumentacją,

copy() — zwraca obiekt zwrócony przez copy(with:).

jest to wygodna metoda dla klas, które przyjmują protokół NSCopying. Wyjątek jest zgłaszany, jeśli nie ma implementacji dla copy(with:).

zrestrukturyzujmy Address class, który stworzyliśmy w fragmencie kodu 2, aby był zgodny z protokołem NSCopying.

w powyższym kodzie,

  • linie od 1 do 14: Address typ klasy jest zgodny z NSCopying i implementujecopy(with:) metodę
  • linia 16: Address typu
  • linia 17: a1 jest przypisana do a2 przy użyciu metody copy(). Spowoduje to utworzenie głębokiej kopii a1 , a następnie przypisanie tej kopii do a2, czyli zostanie utworzony zupełnie nowy obiekt.
  • linie 22 do 25: wszelkie zmiany dokonane w a2 nie będą odzwierciedlać w a1 .

jak wynika z powyższej ilustracji, zarówno a1, jak i a2 wskazują różne miejsca pamięci.

spójrzmy na inny przykład. Tym razem zobaczymy, jak to działa z zagnieżdżonymi typami referencyjnymi — typem referencyjnym zawierającym inny typ referencyjny.

w powyższym kodzie,

  • linia 22: głęboka Kopia p1 jest przypisana do p2 przy użyciu metody copy(). Oznacza to, że każda zmiana w jednym z nich nie może mieć żadnego wpływu na drugi.
  • linie od 27 do 28: p2's name i city wartości są zmieniane. Nie mogą one odzwierciedlać się w p1.
  • linia 30: p1's name jest zgodna z oczekiwaniami, ale jej city? Powinno być "Mumbai" prawda? Ale tego nie widzimy. "Bangalore" było tylko dla p2 prawda? Tak … dokładnie.?

Deep copy…!? Tego się po tobie nie spodziewałem. Powiedziałeś, że wszystko skopiujesz. A teraz zachowujesz się tak. Why oh why..?! Co mam teraz zrobić? ☠ ️

nie panikuj. Spójrzmy, co adresy pamięci mają w tym do powiedzenia.

z powyższej ilustracji widać, że

  • p1 i p2 wskazuj różne miejsca pamięci zgodnie z oczekiwaniami.
  • ale ich address zmienne nadal wskazują tę samą lokalizację. Oznacza to, że nawet po ich głębokim skopiowaniu kopiowane są tylko odniesienia — czyli oczywiście płytka Kopia.

: za każdym razem, gdy kopiujemy Typ odniesienia, domyślnie tworzona jest płytka Kopia, dopóki wyraźnie nie określimy, że powinna być kopiowana głęboko.

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

w powyższej metodzie zaimplementowaliśmy wcześniej dla klasy Person, stworzyliśmy nową instancję kopiując adres z self.address. Spowoduje to skopiowanie tylko odniesienia do obiektu adresu. Z tego powodu zarówno p1, jak i p2's address wskazują tę samą lokalizację.

tak więc kopiowanie obiektu przy użyciu metody copy() nie spowoduje utworzenia prawdziwie głębokiej kopii obiektu.

aby całkowicie powielić obiekt referencyjny: Typ odniesienia wraz ze wszystkimi zagnieżdżonymi typami odniesienia należy skopiować za pomocą metody copy().

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

użycie powyższego kodu w metodzie func copy(with zone: NSZone? = nil) -> Any spowoduje, że wszystko zadziała. Widać to na poniższej ilustracji.

True Deep Copy-typy referencji i wartości

już widzieliśmy, jak możemy utworzyć głęboką kopię typów referencji. Oczywiście możemy to zrobić ze wszystkimi zagnieżdżonymi typami referencyjnymi.

ale co z zagnieżdżonym typem odniesienia w typie wartości, który jest tablicą obiektów lub zmienną typu odniesienia w strukturze, a może krotką? Czy możemy rozwiązać to również za pomocą copy()? Nie możemy. Metoda copy() wymaga implementacji protokołu NSCopying, który działa tylko dla podklas NSObject. Typy wartości nie obsługują dziedziczenia, więc nie możemy używać copy() z nimi.

w linii 2 tylko struktura arr1 jest głęboko kopiowana, ale obiekty Address wewnątrz niej są nadal płytkie. Widać to na poniższej mapie pamięci.

elementy zarówno w arr1, jak i arr2 wskazują te same miejsca pamięci. Dzieje się tak z tego samego powodu — typy referencyjne są domyślnie kopiowane płytko.

serializacja i następnie de-serializacja obiektu zawsze tworzy zupełnie nowy obiekt. Dotyczy zarówno typów wartości, jak i typów referencyjnych.

oto kilka interfejsów API, których możemy użyć do serializacji i de-serializacji danych:

  1. NSCoding-protokół umożliwiający zakodowanie i dekodowanie obiektu w celu archiwizacji i dystrybucji. Będzie działać tylko z obiektami typu class, ponieważ wymaga dziedziczenia z NSObject.
  2. Kodowalne — twórz typy danych kodowalne i dekodowalne w celu zapewnienia zgodności z zewnętrznymi reprezentacjami, takimi jak JSON. Będzie działać zarówno dla typów wartości – struct, array, tuple, basic data types, jak i typów referencyjnych – class.

zrestrukturyzujmy klasę Address nieco dalej, aby była zgodna z protokołem Codable i usuń cały kod NSCopying, który dodaliśmy wcześniej w fragmencie kodu 3.

w powyższym kodzie linie 11-13 stworzą prawdziwą głęboką kopię arr1. Poniżej znajduje się ilustracja, która daje wyraźny obraz lokalizacji pamięci.

Copy on Write

Copy on write to technika optymalizacji, która pomaga zwiększyć wydajność podczas kopiowania typów wartości.

powiedzmy, że kopiujemy pojedynczy ciąg znaków lub Int, a może jakikolwiek inny typ wartości — w takim przypadku nie napotkamy żadnych istotnych problemów z wydajnością. Ale co z kopiowaniem tablicy tysięcy elementów? Czy nadal nie spowoduje to problemów z wydajnością? Co jeśli po prostu go skopiujemy i nie wprowadzimy żadnych zmian w tej kopii? Czy ta dodatkowa pamięć nie jest marnowana w tym przypadku?

tutaj pojawia się koncepcja kopiowania w zapisie — podczas kopiowania każde odniesienie wskazuje na ten sam adres pamięci. Tylko wtedy, gdy jedna z referencji modyfikuje dane bazowe, Swift faktycznie kopiuje oryginalną instancję i dokonuje modyfikacji.

oznacza to, że niezależnie od tego, czy jest to głęboka czy płytka Kopia, nowa kopia nie zostanie utworzona, dopóki nie dokonamy zmiany w jednym z obiektów.

w powyższym kodzie,

  • linia 2: głęboka Kopia arr1 jest przypisana do arr2
  • linii 4 i 5: arr1 i arr2 nadal wskazują na ten sam adres pamięci
  • linia 7: zmiany dokonane w arr2
  • linie 9 i 10: arr1 i arr2 teraz wskazują na różne lokalizacje pamięci

teraz wiesz więcej o głębokich i płytkich kopiach oraz o tym, jak zachowują się w różnych scenariuszach z różnymi typami danych. Możesz wypróbować je z własnym zestawem przykładów i zobaczyć, jakie wyniki uzyskasz.

Czytaj dalej

nie zapomnij przeczytać innych moich artykułów:

  1. wszystko o kodowaniu w Swift 4
  2. wszystko, co zawsze chciałeś wiedzieć o powiadomieniach w iOS
  3. Pokoloruj go gradientami — iOS
  4. kodowanie dla iOS 11: jak przeciągnąć & upuść do kolekcji & tabele
  5. wszystko, co musisz wiedzieć o rozszerzeniach Today (widget) w iOS 10
  6. uicollectionviewcell wybór jest łatwy..!!

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.