cat swift/value-types-reference-types.md
Value types e reference types
La distinzione più importante per capire Swift: quando un dato viene copiato e quando viene condiviso.
Swift è spesso descritto come un linguaggio che preferisce i value types. Questa frase è vera, ma può diventare vaga se non la colleghiamo a un problema pratico.
La domanda concreta è: quando passo un dato a un’altra parte del programma, sto passando una copia indipendente oppure sto condividendo la stessa identità?
Questa differenza cambia il modo in cui ragioni su bug, mutabilità, concorrenza e architettura.
Il problema della condivisione invisibile
Immagina un carrello:
struct Carrello {
var prodotti: [String]
}
var carrelloA = Carrello(prodotti: ["Libro"])
var carrelloB = carrelloA
carrelloB.prodotti.append("Mouse")
print(carrelloA.prodotti)
print(carrelloB.prodotti)
Con una struct, carrelloB parte come copia di carrelloA. Quando modifichi carrelloB, non stai modificando carrelloA.
Questo è il comportamento mentale dei value types: due valori uguali possono evolvere separatamente.
Struct: identità del valore
Una struct è adatta quando ciò che conta è il contenuto.
Due prodotti con gli stessi dati sono, dal punto di vista del dominio, lo stesso valore:
struct Prodotto: Equatable {
let id: String
let nome: String
let prezzoInCent: Int
}
let a = Prodotto(id: "p-1", nome: "Libro", prezzoInCent: 1900)
let b = Prodotto(id: "p-1", nome: "Libro", prezzoInCent: 1900)
print(a == b) // true
Qui non ci interessa dove vivono in memoria. Ci interessa che rappresentino la stessa informazione.
Class: identità dell’oggetto
Una class, invece, ha identità di riferimento. Due variabili possono puntare allo stesso oggetto.
final class SessioneUtente {
var token: String
init(token: String) {
self.token = token
}
}
let sessioneA = SessioneUtente(token: "abc")
let sessioneB = sessioneA
sessioneB.token = "xyz"
print(sessioneA.token) // xyz
sessioneA e sessioneB non sono due sessioni indipendenti. Sono due riferimenti alla stessa sessione.
Questo comportamento è utile quando hai bisogno di identità condivisa, ma è pericoloso se lo usi senza pensarci.
Quando preferire struct
In Swift, parti quasi sempre da struct quando stai modellando dati.
Buoni candidati per struct:
ProdottoIndirizzoPrezzoRigaOrdineConfigurazioneFiltroRicercaCoordinate
Il motivo non è ideologico. È pratico: i value types riducono la condivisione accidentale.
Quando usare class
Una class è utile quando ti serve identità condivisa o integrazione con API che la richiedono.
Buoni candidati per class:
- un oggetto che rappresenta una sessione viva;
- un controller o coordinatore con stato condiviso;
- un wrapper per risorse esterne;
- un oggetto che deve essere osservato da più parti;
- un tipo che deve ereditare da una classe di framework.
In altre parole: usa class quando l’identità dell’oggetto conta.
Il test mentale
Quando devi scegliere tra struct e class, chiediti:
Se passo questo valore a un’altra funzione è quella funzione lo modifica, mi aspetto che il chiamante veda la modifica?
Se la risposta è no, probabilmente vuoi una struct.
Se la risposta è sì, forse vuoi una class, oppure un altro modello esplicito di condivisione.
Esempio nel dominio carrello
Un prodotto è un value type:
struct Prodotto {
let id: String
let nome: String
let prezzoInCent: Int
}
Una riga del carrello è un value type:
struct RigaCarrello {
let prodotto: Prodotto
var quantita: Int
}
Il carrello stesso può essere un value type:
struct Carrello {
var righe: [RigaCarrello]
var totaleInCent: Int {
righe.reduce(0) { totale, riga in
totale + riga.prodotto.prezzoInCent * riga.quantita
}
}
}
Questa scelta rende il carrello facile da testare: dato un insieme di righe, il totale e deterministico.
Errore comune
Un errore frequente e usare una class perché “poi devo modificarla”.
La modificabilità non richiede una classe. Una struct può avere proprietà var:
struct Contatore {
var valore: Int
mutating func incrementa() {
valore += 1
}
}
La parola chiave mutating rende esplicito che il metodo cambia il valore. Anche questo e Swift: quando qualcosa cambia, il linguaggio vuole che si veda.
Checklist
Prima di scegliere class, controlla:
- mi serve davvero identità condivisa?
- voglio che più parti osservino lo stesso stato?
- sto interoperando con un framework che richiede una classe?
- sto usando ereditarietà per un motivo reale?
Se non hai almeno una risposta forte, parti da struct.
Esercizio
Nel dominio carrello, crea questi tipi:
ProdottoRigaCarrelloCarrelloSessioneCheckout
Decidi quali devono essere struct e quali class. Per ogni scelta scrivi una frase: “Uso questo tipo perché…”.
Poi crea due copie di un carrello e modifica solo la seconda. Verifica che la prima resti invariata.