defer è una costruzione di controllo del flusso in Swift che pianifica l'esecuzione di un blocco di codice al momento dell'uscita dall'ambito corrente. Il blocco defer viene eseguito indipendentemente da come l'ambito termina — return, break, throw, fatalError o completamento normale. Secondo la Swift Language Guide (2025), quando più defer sono presenti nello stesso ambito, vengono eseguiti in ordine inverso rispetto alla dichiarazione — l'ultimo dichiarato viene eseguito per primo (LIFO). Questo rende defer indispensabile per la pulizia garantita delle risorse: chiudere descrittori di file, rilasciare blocchi, liberare puntatori temporanei senza il rischio di saltare la pulizia in caso di uscita anticipata.
Punti Chiave
defer è una costruzione di controllo del flusso in Swift, introdotta in Swift 2.0 (2015), che posticipa l'esecuzione del suo blocco fino al termine dell'ambito corrente. La caratteristica principale: defer garantisce che il suo corpo venga eseguito indipendentemente da come l'ambito termina — con successo (return), con errore (throw), prematuramente (break, continue) o fatalmente (fatalError, precondition).
Sintatticamente, defer si scrive come defer { /* codice */ } e può essere posizionato ovunque all'interno dell'ambito. Il compilatore Swift garantisce che il codice all'interno di defer verrà eseguito anche se si verifica un'eccezione o un return tra la dichiarazione di defer e la fine dell'ambito. Questo distingue fondamentalmente defer dal codice normale posizionato alla fine di una funzione, che potrebbe essere saltato in caso di uscita anticipata.
Secondo un articolo di Chris Lattner (Creatore di Swift, 2015), defer è stato ispirato da costruzioni simili in altri linguaggi — defer in Go, finally in Java/Python, scope guard in C++ — ma con un'importante differenza: in Swift, defer viene eseguito alla fine dell'ambito, non immediatamente dopo un blocco try-catch. Questo fornisce un comportamento più prevedibile per la pulizia in funzioni con più punti di uscita.
Utilizzare defer per una gestione simmetrica delle risorse: aprire file → defer { close }, acquisire blocco → defer { unlock }. Questo modello garantisce che il rilascio delle risorse non venga mai dimenticato in nessuna circostanza.
Quando più defer vengono dichiarati nello stesso ambito, vengono eseguiti in ordine inverso rispetto alla dichiarazione (LIFO — Last In, First Out). Ciò significa che l'ultimo defer dichiarato viene eseguito per primo, e il primo viene eseguito per ultimo:
func exampleDeferOrder() {
defer { print("Primo defer") }
defer { print("Secondo defer") }
defer { print("Terzo defer") }
print("Corpo della funzione")
}
// Output:
// Corpo della funzione
// Terzo defer
// Secondo defer
// Primo defer
L'ordine LIFO è importante per la corretta gestione delle risorse annidate. Se il file A viene aperto per primo, poi il file B, devono essere rilasciati in ordine inverso: prima B, poi A. Con defer, ciò avviene automaticamente — dichiarare defer subito dopo l'apertura di ogni risorsa, e l'ordine di pulizia sarà corretto indipendentemente dal numero di punti di uscita della funzione.
Secondo Swift by Sundell (2024), questa caratteristica rende defer ideale per blocchi annidati e transazioni: acquisire blocco → defer { unlock } → acquisire successivo → defer { unlock }. LIFO garantisce che i blocchi vengano rilasciati in ordine inverso rispetto all'acquisizione, prevenendo deadlock.
Il caso d'uso principale di defer è la pulizia garantita delle risorse. Consideriamo il lavoro con il file system. Aprire un file tramite FileHandle richiede una chiusura esplicita — defer garantisce che close venga chiamato in qualsiasi scenario:
func readFile(path: String) throws -> String {
let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
defer { try? handle.close() }
let data = try handle.readToEnd()
guard let data else { throw FileError.empty() }
return String(data: data, encoding: .utf8) ?? ""
// handle.close() verrà chiamato anche in caso di throw o return
}
Un altro scenario tipico sono le animazioni dell'interfaccia con un flag di caricamento. Prima di avviare un caricamento, impostare il flag isLoading = true, e defer lo reimposta a false all'uscita dalla funzione, indipendentemente dal successo o fallimento della richiesta. Ciò impedisce che il flag rimanga true a causa di un errore non gestito, bloccando l'interfaccia per sempre.
Secondo il Bitbucket Engineering Blog (2024), defer viene utilizzato anche per il profiling: registrare il tempo all'inizio di una funzione, e in defer — calcolare e mostrare la differenza. Ciò fornisce misurazioni accurate delle prestazioni per tutti i percorsi di esecuzione, inclusi quelli errati.
defer funziona efficacemente con le funzioni throws. Quando una funzione può lanciare un errore in qualsiasi fase, defer garantisce la pulizia senza duplicare il codice in ogni blocco catch o uscita anticipata con guard:
func processTransaction() throws {
let db = try openDatabase()
defer { closeDatabase(db) }
let user = try fetchUser(from: db)
defer { logAudit(user) }
let result = try performPayment(user)
sendNotification(result)
// closeDatabase(db) e logAudit(user) verranno chiamati
// su qualsiasi throw o return
}
Importante: defer viene eseguito prima che il controllo venga trasferito fuori dal blocco catch, ma dopo che si verifica l'errore. Se un errore viene lanciato all'interno di defer, Swift non consente l'uso di try direttamente all'interno di defer — è necessario try? o try!. Secondo la Documentazione Apple, Swift non consente a un errore di fuoriuscire da defer, poiché ciò violerebbe la garanzia di esecuzione del blocco.
Posizionare defer immediatamente dopo l'acquisizione della risorsa. Questo segue il principio di prossimità: il lettore vede acquisizione e rilascio affiancati, migliorando l'affidabilità del codice e semplificando la revisione del codice.
defer viene eseguito all'uscita dall'ambito in cui è dichiarato. Se defer è dichiarato all'interno di un blocco do, viene eseguito all'uscita da quel blocco, non dalla funzione esterna. Se all'interno di un ciclo for — ad ogni iterazione:
func scopeExample() {
print("start")
do {
defer { print("defer del blocco do") }
print("inside do")
}
// "defer del blocco do" stampa qui
print("after do")
}
// Output: start, inside do, defer del blocco do, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// Output: iteration 1, end iteration 1, iteration 2, end iteration 2, ...
Le variabili catturate da defer vengono lette al momento dell'uscita dall'ambito, non al momento della dichiarazione di defer. Se una variabile cambia tra la dichiarazione di defer e la fine dell'ambito, defer vedrà l'ultimo valore. Questa è un'importante distinzione dalle closure, dove la cattura avviene al momento della creazione. Fare attenzione: le modifiche a una variabile dopo la dichiarazione di defer influenzeranno la sua esecuzione.
Il primo errore — presupporre un ordine di esecuzione diverso da LIFO. Se l'ordine di pulizia è importante e i defer vengono dichiarati nell'ordine sbagliato, le risorse potrebbero essere rilasciate con violazioni delle dipendenze. Soluzione: dichiarare defer immediatamente dopo aver catturato ogni risorsa. Seconda risorsa aperta → defer { close second } prima che la prima venga chiusa.
Il secondo errore — utilizzare defer per logica non correlata alla pulizia. defer è pensato per la pulizia garantita, non per il controllo del flusso principale. Se il codice all'interno di defer influisce sul valore di ritorno, è quasi sempre un errore. defer non può modificare il valore di ritorno di una funzione (a differenza di Java finally, dove return in finally sovrascrive il return originale).
Il terzo errore — lanciare un errore da defer. Swift vieta try all'interno di defer se l'errore potrebbe propagarsi verso l'esterno. Utilizzare try? o try! per le operazioni che potrebbero lanciare un errore, o racchiuderle in una funzione separata senza throws. Secondo O’Reilly “Swift in Depth” (2025), una buona pratica è rendere le funzioni di pulizia non lancianti (non-throwing) o gestire gli errori all'interno di defer.
Domande Frequenti
defer è una costruzione Swift che posticipa l'esecuzione di un blocco fino all'uscita dall'ambito corrente. Il blocco viene sempre eseguito — su return, throw, break o completamento normale. Viene utilizzato per la pulizia garantita delle risorse: chiudere file, rilasciare blocchi.
In ordine inverso di dichiarazione (LIFO) — l'ultimo defer dichiarato viene eseguito per primo. Questo garantisce una corretta pulizia delle risorse annidate: se la risorsa B viene aperta dopo A, verrà chiusa prima di A, prevenendo dipendenze da risorse già liberate.
Non direttamente — Swift impedisce la propagazione di errori da defer. Utilizzare try? o try! per le operazioni che potrebbero lanciare un errore. La pratica migliore è rendere le funzioni di pulizia non lancianti o gestire gli errori all'interno di defer senza propagarli verso l'esterno.
defer è legato a un ambito e viene eseguito a qualsiasi uscita, inclusi return, throw e break. finally (in altri linguaggi) è legato a try-catch e viene eseguito solo in presenza di try. Swift non ha finally — defer copre completamente questo scenario e funziona per qualsiasi ambito, non solo per la gestione degli errori.
Sì, defer legge le variabili al momento dell'uscita dall'ambito, non al momento della dichiarazione. Se una variabile cambia dopo la dichiarazione di defer, il blocco defer vedrà l'ultimo valore. Questo differisce dalle closure normali, dove la cattura è fissata al momento della creazione.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche