de Payal Gupta

Copiere profundă vs. copie superficială – și cum le puteți folosi în Swift

Copiere profunda vs copie superficiala si cum le puteti

Copierea unui obiect a fost întotdeauna o parte esențială în paradigma codării. Fie că este în Swift, Objective-C, JAVA sau în orice altă limbă, va trebui întotdeauna să copiem un obiect pentru a fi utilizat în contexte diferite.

În acest articol, vom discuta în detaliu cum să copiați diferite tipuri de date în Swift și cum se comportă în diferite circumstanțe.

Valoare și tipuri de referință

Toate tipurile de date din Swift se încadrează în general în două categorii, și anume tipuri de valori și tipuri de referință.

  • Tipul valorii – fiecare instanță păstrează o copie unică a datelor sale. Tipurile de date care se încadrează în această categorie includ – all the basic data types, struct, enum, array, tuples.
  • Tipul de referință – instanțele partajează o singură copie a datelor, iar tipul este de obicei definit ca un class.

Cea mai distinctivă caracteristică a ambelor tipuri constă în comportamentul lor de copiere.

ad-banner

Ce este copia Deep and Shallow?

O instanță, indiferent dacă este un tip de valoare sau un tip de referință, poate fi copiată în unul din următoarele moduri:

Copie profundă – Duplică totul

  • Cu o copie profundă, orice obiect indicat de sursă este copiat, iar copia este indicată de destinație. Deci vor fi create două obiecte complet separate.
  • Colecții – O copie profundă a unei colecții este două colecții cu toate elementele din colecția originală duplicate.
  • Mai puțin predispuse la condițiile cursei și funcționează bine într-un mediu cu mai multe fire – modificările unui obiect nu vor avea niciun efect asupra altui obiect.
  • Tipuri de valori sunt copiate profund.

În codul de mai sus,

  • Linia 1: arr1 – matrice (un tip de valoare) de șiruri
  • Randul 2: arr1 este atribuit arr2. Aceasta va crea o copie profundă a arr1 și apoi atribuiți acea copie către arr2
  • Liniile 7-11: orice modificări efectuate în arr2 nu reflecta în arr1 .

Iată ce este copia profundă – instanțe complet separate. Același concept funcționează cu toate tipurile de valori.

În unele scenarii, atunci când un tip de valoare conține tipuri de referință imbricate, copia profundă dezvăluie un alt tip de comportament. Vom vedea asta în secțiunile viitoare.

Copie superficială – Duplicează cât mai puțin posibil

  • Cu o copie superficială, orice obiect indicat de sursă este indicat și de destinație. Deci, un singur obiect va fi creat în memorie.
  • Colecții – O copie superficială a unei colecții este o copie a structurii colecției, nu a elementelor. Cu o copie superficială, două colecții împărtășesc acum elementele individuale.
  • Mai repede – se copiază doar referința.
  • Copiere tipuri de referință creează o copie superficială.

În codul de mai sus,

  • Liniile 1-8: Address tipul clasei
  • Linia 10: a1 – un exemplu de Address tip
  • Linia 11: a1 este atribuit a2. Aceasta va crea o copie superficială a a1 și apoi atribuiți acea copie către a2 , care este doar referința în care este copiat a2.
  • Liniile 16-19: orice modificări efectuate în a2 se va reflecta cu siguranță în a1 .
Copiere profunda vs copie superficiala si cum le puteti

În ilustrația de mai sus, putem vedea că ambele a1 și a2 indicați aceeași adresă de memorie.

Copierea profundă a tipurilor de referință

De acum, știm că ori de câte ori încercăm să copiem un tip de referință, se copiază doar referința la obiect. Nu este creat niciun obiect nou. Ce se întâmplă dacă vrem să creăm un obiect complet separat?

Putem crea o copie profundă a tipului de referință folosind copy() metodă. In conformitate cu documentație,

copy () – Returnează obiectul returnat de copy(with:).

Aceasta este o metodă de comoditate pentru clasele care adoptă NSCopying protocol. Se ridică o excepție dacă nu există nicio implementare pentru copy(with:).

Să restructurăm Address class am creat în fragmentul de cod 2 pentru a ne conforma cu NSCopying protocol.

În codul de mai sus,

  • Liniile 1-14: Address tipul clasei este conform NSCopying și unelte copy(with:) metodă
  • Linia 16: a1 – un exemplu de Address tip
  • Linia 17: a1 este atribuit a2 folosind copy() metodă. Aceasta va crea o copie profundă a a1 și apoi atribuiți acea copie către a2 , care este un obiect complet nou va fi creat.
  • Liniile 22-25: orice modificări efectuate în a2 nu se va reflecta în a1 .
1612016414 891 Copiere profunda vs copie superficiala si cum le puteti

După cum reiese din ilustrația de mai sus, ambele a1 și a2 indicați spre diferite locații de memorie.

Să ne uităm la un alt exemplu. De data aceasta vom vedea cum funcționează tipuri de referință imbricate – un tip de referință care conține un alt tip de referință.

În codul de mai sus,

  • Linia 22: o copie profundă a p1 este atribuit p2 folosind copy() metodă. Aceasta implică faptul că orice modificare a uneia dintre ele nu trebuie să aibă niciun efect asupra celeilalte.
  • Liniile 27-28: p2’s name și city valorile sunt schimbate. Acestea nu trebuie să se reflecte în p1.
  • Linia 30: p1’s name este așa cum era de așteptat, dar este city? Ar trebui să fie “Mumbai” nu ar trebui? Dar nu putem vedea că se întâmplă asta. “Bangalore” a fost doar pentru p2 dreapta? Da … exact.?

Copie profundă …!? Tpălăria nu era așteptată de la tine. Ai spus că vei copia totul. Și acum te comporti așa. De ce, de ce ..?! Ce fac acum? ☠

Nu intra în panică. Să vedem ce adrese de memorie are de spus în acest sens.

1612016414 692 Copiere profunda vs copie superficiala si cum le puteti

Din ilustrația de mai sus, putem vedea asta

  • p1 și p2 indicați spre diferite locații de memorie, așa cum era de așteptat.
  • Dar lor address variabilele indică în continuare aceeași locație. Aceasta înseamnă că, chiar și după copierea lor profundă, sunt copiate doar referințele – adică a copie superficială desigur.

Vă rugăm să rețineți: de fiecare dată când copiem un tip de referință, se creează în mod implicit o copie superficială până când specificăm în mod explicit că ar trebui copiat profund.

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

În metoda de mai sus am implementat mai devreme pentru Person clasă, am creat o nouă instanță prin copierea adresei cu self.address . Aceasta va copia doar referința la obiectul adresă. Acesta este motivul pentru care ambele p1 și p2’s address indicați spre aceeași locație.

Deci, copierea obiectului folosind copy() metoda nu va crea o copie profundă adevărată a obiectului.

Pentru a duplica complet un obiect de referință: tipul de referință împreună cu toate tipurile de referință imbricate trebuie copiate cu copy() metodă.

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

Folosind codul de mai sus în func copy(with zone: NSZone? = nil) -> Orice metodă va face ca totul să funcționeze. Puteți vedea asta din ilustrația de mai jos.

1612016415 319 Copiere profunda vs copie superficiala si cum le puteti

True Deep Copy – Tipuri de referință și valoare

Am văzut deja cum putem crea o copie profundă a tipurilor de referință. Desigur, putem face asta cu toate tipurile de referință imbricate.

Dar ce zici de tipul de referință imbricat într-un tip de valoare, adică o matrice de obiecte sau o variabilă de tip de referință într-o structură sau poate un tuplu? Putem rezolva acest lucru folosind copy() de asemenea? Nu, nu putem, de fapt. copy() metoda necesită implementarea NSCopying protocol care funcționează numai pentru NSObject subclasele. Tipurile de valori nu acceptă moștenirea, deci nu putem folosi copy() cu ei.

În linia 2, doar structura arr1 este copiat profund, dar Address obiectele din interiorul acestuia sunt încă copiate superficial. Puteți vedea asta din harta de memorie de mai jos.

1612016415 368 Copiere profunda vs copie superficiala si cum le puteti

Elementele din ambele arr1 și arr2 ambele indică aceleași locații de memorie. Acest lucru se datorează aceluiași motiv – tipurile de referință sunt copiate superficial în mod implicit.

Serializarea și apoi serializarea un obiect creează întotdeauna un obiect nou. Este valabil atât pentru tipurile de valori, cât și pentru tipurile de referință.

Iată câteva API-uri pe care le putem folosi pentru a serializa și de-serializa date:

  1. Codificare NS – Un protocol care permite codificarea și decodarea unui obiect pentru arhivare și distribuție. Va funcționa doar cu class tastați obiecte deoarece necesită moștenire de la NSObject .
  2. Codabil – Faceți tipurile de date codificate și decodabile pentru compatibilitate cu reprezentări externe, cum ar fi JSON. Va funcționa pentru ambele tipuri de valori – struct, array, tuple, basic data typesprecum și tipuri de referință – class .

Să restructurăm Address clasa un pic mai departe pentru a se conforma la Codable protocol și eliminați toate NSCopying cod pe care l-am adăugat mai devreme în fragmentul de cod 3.

În codul de mai sus, liniile 11-13 vor crea o copie profundă adevărată a arr1. Mai jos este ilustrația care oferă o imagine clară a locațiilor memoriei.

1612016416 816 Copiere profunda vs copie superficiala si cum le puteti

Copie pe scriere

Copierea la scriere este o tehnică de optimizare care ajută la creșterea performanței la copierea tipurilor de valori.

Să presupunem că copiem un singur șir sau Int sau poate orice alt tip de valoare – în acest caz nu ne vom confrunta cu probleme de performanță cruciale. Dar ce se întâmplă atunci când copiem o serie de mii de elemente? Încă nu va crea probleme de performanță? Ce se întâmplă dacă doar o copiem și nu facem modificări la copia respectivă? Nu este o memorie suplimentară pe care am folosit-o doar o risipă în acest caz?

Aici vine conceptul de copiere în scriere – la copiere, fiecare referință indică aceeași adresă de memorie. Abia atunci când una dintre referințe modifică datele subiacente, Swift copiază de fapt instanța originală și face modificarea.

Adică, fie că este vorba de o copie profundă sau de o copie superficială, o copie nouă nu va fi creată până nu vom face o modificare a unuia dintre obiecte.

În codul de mai sus,

  • Randul 2: o copie profundă a arr1 este atribuit arr2
  • Liniile 4 și 5: arr1 și arr2 indică în continuare aceeași adresă de memorie
  • Linia 7: modificări făcute în arr2
  • Liniile 9 și 10: arr1 și arr2 indicând acum diferite locații de memorie

Acum știți mai multe despre copiile profunde și superficiale și despre cum se comportă în diferite scenarii cu diferite tipuri de date. Le puteți încerca cu propriul set de exemple și puteți vedea ce rezultate obțineți.

Lecturi suplimentare

Nu uitați să citiți celelalte articole ale mele:

  1. Totul despre Codable în Swift 4
  2. Tot ce ai vrut mereu să știi despre notificări în iOS
  3. Colorează-l cu GRADIENTS – iOS
  4. Codificare pentru iOS 11: Cum să glisați și să fixați în colecții și tabele
  5. Tot ce trebuie să știți despre Extensiile de azi (Widget) în iOS 10
  6. Selecția UICollectionViewCell a fost ușoară .. !!

Nu ezitați să lăsați comentarii în cazul în care aveți întrebări.