cat swift/closure-capture-liste-cattura.md
Closure capture e liste di cattura
Le closure possono catturare valori e oggetti: capire capture list, self, weak e snapshot evita bug sottili.
Una closure può usare valori dichiarati fuori dal suo corpo. Quando lo fa, li cattura.
let prefisso = "Totale"
let descrivi = { (valore: Int) in
"\(prefisso): \(valore)"
}
La closure cattura prefisso.
Questo è comodo, ma quando la closure cattura oggetti o stato mutabile possono nascere bug.
Cattura di valori
var totale = 100
let stampa = {
print(totale)
}
totale = 200
stampa() // 200
La closure vede il valore aggiornato della variabile catturata.
Se vuoi catturare uno snapshot:
var totale = 100
let stampa = { [totale] in
print(totale)
}
totale = 200
stampa() // 100
La capture list [totale] cattura il valore al momento della creazione della closure.
Cattura di self
Dentro una classe:
final class ViewModel {
var onUpdate: (() -> Void)?
func configura() {
onUpdate = {
self.aggiorna()
}
}
func aggiorna() {}
}
La closure cattura self fortemente. Se self mantiene la closure, hai un ciclo.
weak self
onUpdate = { [weak self] in
self?.aggiorna()
}
self diventa opzionale dentro la closure. Se l’oggetto è stato rilasciato, la closure non fa nulla.
Questa è la forma più comune per callback che possono vivere quanto o più dell’oggetto.
guard let self
Quando vuoi evitare optional chaining ripetuto:
onUpdate = { [weak self] in
guard let self else {
return
}
self.aggiorna()
self.salvaStato()
}
Questa forma mantiene self vivo solo durante l’esecuzione della closure.
unowned self
onUpdate = { [unowned self] in
self.aggiorna()
}
Usalo solo quando sei certo che la closure non verrà chiamata dopo la deallocazione di self.
Se sbagli, crash.
Nel dubbio, usa weak.
Capture list con proprietà specifiche
Non devi catturare per forza tutto self.
final class Presenter {
let formatter: PrezzoFormatter
init(formatter: PrezzoFormatter) {
self.formatter = formatter
}
func render(prezzi: [Prezzo]) -> [String] {
prezzi.map { [formatter] prezzo in
formatter.stringa(da: prezzo)
}
}
}
Qui catturi formatter, non l’intero oggetto.
Questo riduce accoppiamento e rischi.
Closure escaping
Una closure escaping sopravvive alla chiamata della funzione.
func carica(completamento: @escaping () -> Void) {
self.completamento = completamento
}
Le closure escaping sono più rischiose per i retain cycle, perché possono essere conservate.
Con async/await userai meno callback, ma le closure restano ovunque: UI, Combine, timer, gesture, sorting, map/filter.
Timer e task ripetuti
Un caso tipico:
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.tick()
}
Se il timer è mantenuto da self è la closure cattura self, hai un ciclo.
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.tick()
}
Checklist
Quando scrivi una closure, chiediti:
- cattura
self? - la closure viene salvata da
self? - la closure è escaping?
- può essere chiamata dopo la deallocazione dell’oggetto?
- posso catturare una proprietà invece di tutto
self? - serve uno snapshot di un valore?
weakè sufficiente?
Esercizio
Crea una classe CouponViewModel con:
- una proprietà
onChange: (() -> Void)?; - una funzione
configuraCallback; - una closure che inizialmente cattura
selfforte; - un
deinitconprint.
Verifica il ciclo, poi correggilo con [weak self].
Infine crea un esempio con [totale] per catturare uno snapshot di un valore.