cat swift/copy-on-write-senza-magia.md
Copy-on-write senza magia
Swift offre semantica di valore con ottimizzazioni intelligenti: capire copy-on-write aiuta a non temere copie e a non abusare delle classi.
Swift incoraggia l’uso dei value types. Una domanda sorge subito: se copio array, stringhe e struct grandi, non sto sprecando memoria?
La risposta breve è: spesso no, grazie al copy-on-write.
Copy-on-write significa: condividi lo storage finché nessuno modifica. Quando arriva una modifica, allora fai una copia se serve.
Il comportamento mentale
var a = [1, 2, 3]
var b = a
b.append(4)
print(a) // [1, 2, 3]
print(b) // [1, 2, 3, 4]
Dal punto di vista del programmatore, a e b sono valori indipendenti.
Sotto, Swift può condividere lo storage dell’array fino alla modifica. Quando b cambia, preserva a.
Semantica semplice, implementazione efficiente
Questo è il bello del copy-on-write: ti permette di ragionare come se ogni copia fosse indipendente, senza pagare sempre il costo di duplicare subito tutta la memoria.
Array, Dictionary, Set e String usano strategie di questo tipo.
var originale = "Swift"
var copia = originale
copia.append(" 6")
print(originale) // Swift
print(copia) // Swift 6
Quando avviene la copia
La copia dello storage può avvenire quando modifichi un valore che condivide storage con un altro valore.
var primo = Array(0..<10_000)
var secondo = primo
secondo[0] = 999
Qui secondo deve diventare indipendente da primo.
Non devi memorizzare dettagli interni; devi capire la conseguenza: copiare un array non è necessariamente costoso subito, ma modificarlo dopo una condivisione può esserlo.
Evitare classi per paura delle copie
Un errore comune:
final class Carrello {
var righe: [RigaCarrello]
}
Scelto solo per “non copiare”. Ma così introduci condivisione e mutabilità globale.
Spesso è meglio:
struct Carrello {
var righe: [RigaCarrello]
}
Poi misuri. Se davvero c’è un problema, ottimizzi con dati alla mano.
isKnownUniquelyReferenced
Puoi implementare copy-on-write nei tuoi tipi usando una classe interna e isKnownUniquelyReferenced.
Esempio didattico:
final class Storage {
var righe: [RigaCarrello]
init(righe: [RigaCarrello]) {
self.righe = righe
}
}
struct CarrelloCOW {
private var storage: Storage
init(righe: [RigaCarrello]) {
self.storage = Storage(righe: righe)
}
var righe: [RigaCarrello] {
storage.righe
}
mutating func aggiungi(_ riga: RigaCarrello) {
if !isKnownUniquelyReferenced(&storage) {
storage = Storage(righe: storage.righe)
}
storage.righe.append(riga)
}
}
Questo non è codice da scrivere spesso. Serve a capire il meccanismo: semantica di valore fuori, storage condiviso dentro finché è sicuro.
Quando preoccuparsi
Preoccupati del costo delle copie quando:
- lavori con collection molto grandi;
- modifichi spesso copie derivate;
- sei in un ciclo caldo;
- i profiler mostrano allocazioni o copie rilevanti;
- stai costruendo una libreria performance-sensitive.
Non preoccuparti per ogni passaggio di valore in codice applicativo normale.
Segnali di copy-on-write inefficiente
Esempio sospetto:
func aggiungiMolti(_ elementi: [Elemento], a base: [Elemento]) -> [Elemento] {
var risultato = base
for elemento in elementi {
risultato = risultato + [elemento]
}
return risultato
}
Meglio:
func aggiungiMolti(_ elementi: [Elemento], a base: [Elemento]) -> [Elemento] {
var risultato = base
risultato.reserveCapacity(base.count + elementi.count)
risultato.append(contentsOf: elementi)
return risultato
}
Qui riduci riallocazioni e copie intermedie.
Copy-on-write e API design
Se una funzione restituisce una nuova versione di un valore:
func aggiungendo(_ riga: RigaCarrello) -> Carrello
non significa automaticamente che stai copiando tutto in modo ingenuo. Il modello è chiaro; l’implementazione può essere ottimizzata.
Checklist
Ricorda:
- copy-on-write preserva semantica di valore;
- copiare una collection non significa sempre duplicare subito;
- modificare una copia condivisa può causare copia dello storage;
- non scegliere classi solo per paura delle copie;
- usa
reserveCapacityquando costruisci collection grandi; - misura prima di ottimizzare.
Esercizio
Scrivi due funzioni che aggiungono molti elementi a un array:
- una con
risultato = risultato + [elemento]; - una con
reserveCapacityeappend.
Poi spiega quale versione preferisci e perché. Non serve misurare ancora: per ora vogliamo riconoscere la forma più efficiente.