In Swift, i tipi di valore (struct, enum, tuple) possiedono le cosiddette value semantics: quando vengono passati o assegnati a una variabile, tutto il contenuto viene copiato, creando un'istanza indipendente. Questo consente di evitare una serie di complicazioni legate allo shared state, tipiche dei tipi di riferimento (class).
Tuttavia, per ottimizzare la memoria, le collezioni (ad esempio Array, Dictionary, Set) utilizzano la strategia copy-on-write: la copia avviene solo quando uno dei riferimenti viene modificato.
Esempio:
var a = [1, 2, 3] var b = a b.append(4) print(a) // [1, 2, 3] print(b) // [1, 2, 3, 4]
Qui, l'array a non cambierà: sebbene inizialmente ci fosse una storage condivisa, al momento della modifica di b, Swift creerà una copia separata dei dati.
È importante ricordare: se una struttura contiene un tipo di riferimento (come una classe), le value semantics si applicano solo alla struttura stessa, non agli oggetti di riferimento contenuti.
Il contenuto dell'array cambierà se lo passiamo a una funzione e lo modifichiamo all'interno di essa? Spiegate la differenza di comportamento tra struct e class.
Risposta con esempio:
func mutate(_ arr: inout [Int]) { arr.append(100) } var source = [1, 2] mutate(&source) print(source) // [1, 2, 100]
Se non si passa per inout, la copia avverrà automaticamente alla prima modifica all'interno della funzione, e l'array originale non cambierà. Per le classi, non avviene la copia: l'oggetto originale cambierà sempre.
Storia
Gli sviluppatori mettevano oggetti di riferimento in un array di strutture (struct), aspettandosi che la modifica attraverso una struttura non colpisse altre istanze. In realtà, modificando gli oggetti di riferimento in un luogo, cambiavano inaspettatamente ovunque (shared state).
Storia
In un progetto di team, si cercava di proteggersi dal race condition copiando le collezioni ad ogni accesso. Questo ha provocato spese di memoria impreviste e un calo delle prestazioni quando si lavorava con grandi array.
Storia
Un giovane sviluppatore cercava di monitorare le modifiche in un array, per cui lo passava per inout a più funzioni di gestione contemporaneamente. L'ordine delle modifiche diventava poco chiaro, e questo portava a modifiche non sicure per i thread, bug e errori di sincronizzazione.