di Payal Gupta

Copiare un oggetto è sempre stata una parte essenziale nel paradigma di codifica. Sia in Swift, Objective-C, JAVA o qualsiasi altro linguaggio, avremo sempre bisogno di copiare un oggetto per l’uso in contesti diversi.

In questo articolo, discuteremo in dettaglio come copiare diversi tipi di dati in Swift e come si comportano in circostanze diverse.

Tipi di valore e di riferimento

Tutti i tipi di dati in Swift rientrano ampiamente in due categorie, vale a dire tipi di valore e tipi di riferimento.

  • Tipo di valore: ogni istanza conserva una copia univoca dei propri dati. I tipi di dati che rientrano in questa categoria includono – all the basic data types, struct, enum, array, tuples.
  • Tipo di riferimento: le istanze condividono una singola copia dei dati e il tipo viene solitamente definito come class.

La caratteristica più distintiva di entrambi i tipi risiede nel loro comportamento di copia.

Che cos’è la copia profonda e superficiale?

Un’istanza, che si tratti di un tipo di valore o di un tipo di riferimento, può essere copiata in uno dei seguenti modi:

Deep copy — Duplica tutto

  • Con una deep copy, qualsiasi oggetto puntato dall’origine viene copiato e la copia viene puntata dalla destinazione. Quindi verranno creati due oggetti completamente separati.
  • Collezioni-Una copia profonda di una collezione è due collezioni con tutti gli elementi della collezione originale duplicati.
  • Meno incline alle condizioni di gara e si comporta bene in un ambiente multithread — le modifiche in un oggetto non avranno alcun effetto su un altro oggetto.
  • I tipi di valore vengono copiati in profondità.

Nel codice di cui sopra,

  • Riga 1:
  • La riga 2: arr1 è assegnata a arr2. Questo creerà una copia profonda di arr1 e quindi assegnerà quella copia a arr2
  • Righe da 7 a 11: tutte le modifiche apportate in arr2 non riflettono in arr1.

Questo è ciò che deep copy è — istanze completamente separate. Lo stesso concetto funziona con tutti i tipi di valore.

In alcuni scenari, cioè quando un tipo di valore contiene tipi di riferimento nidificati, deep copy rivela un diverso tipo di comportamento. Lo vedremo nelle prossime sezioni.

Copia superficiale — Duplica il meno possibile

  • Con una copia superficiale, qualsiasi oggetto puntato dalla sorgente viene anche puntato dalla destinazione. Quindi verrà creato solo un oggetto nella memoria.
  • Collezioni-Una copia superficiale di una raccolta è una copia della struttura della raccolta, non degli elementi. Con una copia superficiale, due collezioni ora condividono i singoli elementi.
  • Più veloce: viene copiato solo il riferimento.
  • La copia dei tipi di riferimento crea una copia superficiale.

Nel codice di cui sopra,

  • Righe da 1 a 8: Address tipo di classe
  • Riga 10: a1 — un’istanza di Address tipo
  • Riga 11: a1 viene assegnata a a2. Questo creerà una copia superficiale di a1 e quindi assegnerà quella copia a a2, cioè solo il riferimento viene copiato in a2.
  • Linee da 16 a 19: eventuali modifiche apportate in a2 rifletteranno sicuramente in a1.

Nell’illustrazione sopra, possiamo vedere che sia a1 che a2 puntano allo stesso indirizzo di memoria.

Copiare profondamente i tipi di riferimento

A partire da ora, sappiamo che ogni volta che proviamo a copiare un tipo di riferimento, viene copiato solo il riferimento all’oggetto. Non viene creato alcun nuovo oggetto. Cosa succede se vogliamo creare un oggetto completamente separato?

Possiamo creare una copia approfondita del tipo di riferimento utilizzando il metodo copy(). Secondo la documentazione,

copy () – Restituisce l’oggetto restituito da copy(with:).

Questo è un metodo di convenienza per le classi che adottano il protocollo NSCopying. Un’eccezione viene sollevata se non esiste alcuna implementazione per copy(with:).

Ristrutturiamo il Address class che abbiamo creato nello Snippet di codice 2 per conformarci al protocollo NSCopying.

Nel codice di cui sopra,

  • Linee da 1 a 14: Address tipo di classe conforme a NSCopying e implementa copy(with:) metodo
  • Linea 16: a1 – un’istanza di Address tipo
  • Linea 17: a1 viene assegnato a a2 utilizzando il metodo copy(). Questo creerà una copia profonda di a1 e quindi assegnerà quella copia a a2, ovvero verrà creato un oggetto completamente nuovo.
  • Righe da 22 a 25: eventuali modifiche apportate in a2 non si rifletteranno in a1.

Come è evidente dall’illustrazione sopra, sia a1 che a2 puntano a diverse posizioni di memoria.

Diamo un’occhiata a un altro esempio. Questa volta vedremo come funziona con i tipi di riferimento nidificati, un tipo di riferimento contenente un altro tipo di riferimento.

Nel codice di cui sopra,

  • Riga 22: una copia profonda di p1 viene assegnata a p2 utilizzando il metodo copy(). Ciò implica che qualsiasi cambiamento in uno di essi non deve avere alcun effetto sull’altro.
  • Righe da 27 a 28: p2's name e city i valori vengono modificati. Questi non devono riflettere in p1.
  • Linea 30: p1's name è come previsto, ma è city? Dovrebbe essere "Mumbai" non dovrebbe? Ma non possiamo vederlo accadere. "Bangalore" era solo per p2 giusto? Sì exactly esattamente.?

Copia profonda!!? Non era previsto da te. Hai detto che copierai tutto. E ora ti comporti cosi’. Perché oh perché..?! Cosa faccio adesso? ☠️

Niente panico. Diamo un’occhiata a quali indirizzi di memoria ha da dire in questo.

Dall’illustrazione di cui sopra, possiamo vedere che

  • p1 e p2 punta a diverse posizioni di memoria come previsto.
  • Ma le loro variabili address puntano ancora alla stessa posizione. Ciò significa che anche dopo averli copiati profondamente, vengono copiati solo i riferimenti, cioè una copia superficiale, ovviamente.

Si prega di notare: ogni volta che copiamo un tipo di riferimento, viene creata una copia superficiale per impostazione predefinita finché non specifichiamo esplicitamente che deve essere copiata in profondità.

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

Nel metodo sopra implementato in precedenza per la classe Person, abbiamo creato una nuova istanza copiando l’indirizzo con self.address. Questo copierà solo il riferimento all’oggetto address. Questo è il motivo per cui sia p1 che p2's address puntano alla stessa posizione.

Quindi, copiare l’oggetto usando il metodo copy() non creerà una vera copia profonda dell’oggetto.

Per duplicare completamente un oggetto di riferimento: il tipo di riferimento insieme a tutti i tipi di riferimento nidificati deve essere copiato con il metodo copy().

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

Usando il codice sopra nel func copy(with zone: NSZone? = nil) -> Qualsiasi metodo farà funzionare tutto. Si può vedere che dalla figura qui sotto.

True Deep Copy-Reference e Value types

Abbiamo già visto come possiamo creare una copia profonda dei tipi di riferimento. Naturalmente possiamo farlo con tutti i tipi di riferimento nidificati.

Ma per quanto riguarda il tipo di riferimento nidificato in un tipo di valore, ovvero una matrice di oggetti, o una variabile di tipo di riferimento in una struttura o forse una tupla? Possiamo risolverlo usando anche copy()? No, non possiamo, in realta’. Il metodo copy() richiede l’implementazione del protocollo NSCopying che funziona solo per sottoclassi NSObject. I tipi di valore non supportano l’ereditarietà, quindi non possiamo usare copy() con loro.

Nella riga 2, solo la struttura di arr1 viene copiata in profondità, ma gli oggetti Address al suo interno sono ancora copiati in modo superficiale. Si può vedere che dalla mappa di memoria qui sotto.

Gli elementi in entrambi arr1 e arr2 puntano entrambi alle stesse posizioni di memoria. Ciò è dovuto allo stesso motivo: i tipi di riferimento sono copiati in modo superficiale per impostazione predefinita.

Serializzare e quindi de-serializzare un oggetto crea sempre un nuovo oggetto. È valido sia per i tipi di valore che per i tipi di riferimento.

Ecco alcune API che possiamo utilizzare per serializzare e de-serializzare i dati:

  1. NSCoding-Un protocollo che consente di codificare e decodificare un oggetto per l’archiviazione e la distribuzione. Funzionerà solo con class oggetti di tipo in quanto richiede l’ereditarietà da NSObject.
  2. Codable-Rendi i tuoi tipi di dati codificabili e decodificabili per compatibilità con rappresentazioni esterne come JSON. Funzionerà sia per i tipi di valore – struct, array, tuple, basic data types che per i tipi di riferimento – class.

Ristrutturiamo un po ‘ più la classe Address per conformarci al protocollo Codable e rimuovere tutto il codice NSCopying che abbiamo aggiunto in precedenza in Code Snippet 3.

Nel codice precedente, le righe 11-13 creeranno una vera copia profonda di arr1. Di seguito è riportata l’illustrazione che fornisce un’immagine chiara delle posizioni di memoria.

Copy on Write

Copy on write è una tecnica di ottimizzazione che aiuta a migliorare le prestazioni durante la copia di tipi di valore.

Diciamo che copiamo una singola stringa o Int o forse qualsiasi altro tipo di valore-in questo caso non affronteremo problemi di prestazioni cruciali. Ma che dire quando copiamo una serie di migliaia di elementi? Non creerà ancora problemi di prestazioni? E se lo copiamo e non apportiamo modifiche a quella copia? Non è che la memoria in più abbiamo usato solo uno spreco in quel caso?

Ecco il concetto di Copia in scrittura — quando si copia, ogni riferimento punta allo stesso indirizzo di memoria. È solo quando uno dei riferimenti modifica i dati sottostanti che Swift copia effettivamente l’istanza originale e apporta la modifica.

Cioè, che si tratti di una copia profonda o di una copia superficiale, una nuova copia non verrà creata finché non apportiamo una modifica a uno degli oggetti.

Nel codice di cui sopra,

  • Riga 2: una copia profonda di arr1 viene assegnata a arr2
  • Righe 4 e 5: arr1 earr2 puntano ancora allo stesso indirizzo di memoria
  • Riga 7: modifiche apportate in arr2
  • Righe 9 e 10: arr1 earr2 ora puntano a diverse posizioni di memoria

Ora sai di più sulle copie profonde e superficiali e su come si comportano in diversi scenari con diversi tipi di dati. Puoi provarli con il tuo set di esempi e vedere quali risultati ottieni.

Ulteriori letture

Non dimenticare di leggere i miei altri articoli:

  1. Tutto Codificabile in Swift 4
  2. Tutto quello che hai sempre voluto sapere sulle notifiche in iOS
  3. Colore con SFUMATURE — iOS
  4. Codifica per iOS 11: Come trascinare & cadere in collezioni & tabelle
  5. Tutto quello che devi sapere su Oggi Estensioni (Widget) in iOS 10
  6. UICollectionViewCell selezione facile..!!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.