L'operatore defer è stato introdotto in Swift per la pulizia sicura e garantita delle risorse o per l'esecuzione di codice al termine della visibilità, paragonabile a finally/using/RAII in altri linguaggi.
L'inizializzazione a più fasi, il lavoro con file, scenari di uscita multipli dalla funzione - richiedono la liberazione garantita delle risorse o l'esecuzione della logica (chiudere un file, sbloccare un mutex, restituire un oggetto a un pool, ripristinare uno stato temporaneo). Prima dell'introduzione di defer, tutto doveva essere fatto manualmente in ogni return.
defer garantisce l'esecuzione del suo codice all'uscita dall'attuale ambito, indipendentemente dal fatto che l'uscita sia normale o tramite throw. Si possono dichiarare più defer: verranno eseguiti in ordine inverso rispetto alla loro dichiarazione.
Esempio di liberazione di una risorsa:
func processFile(path: String) throws { let file = try openFile(path) defer { file.close() } // Sarà certamente chiamato anche in caso di errori // ... lavoro con file ... }
Caratteristiche principali:
Può defer catturare variabili per riferimento e come influisce sul comportamento delle chiusure?
Sì, defer cattura tutte le variabili utilizzate al momento della chiamata, secondo le regole di cattura delle chiusure (copia per i tipi valore, riferimento per i tipi riferimento). È un errore catturare una variabile esterna mutabile - potrebbe cambiare al momento dell'esecuzione del defer.
Come funziona defer annidato all'interno di cicli e funzioni?
Se defer è dichiarato all'interno di un ciclo, viene eseguito ad ogni completamento dell'iterazione dell'ambito:
for _ in 1...3 { defer { print("Fine dell'iterazione") } print("Dentro") }
Produrrà:
Dentro
Fine dell'iterazione
Dentro
Fine dell'iterazione
Dentro
Fine dell'iterazione
Può defer non essere eseguito?
L'esecuzione del codice defer è garantita solo se l'ambito viene effettivamente lasciato. Ma se il processo termina in modo anomalo (fatalError, crash), o viene chiamato exit, defer non viene eseguito. Inoltre, defer non funziona a livello di metodi degli oggetti - solo nell'ambito di una funzione/blocco.
Nel codice, dopo aver aperto un file e lavorato su di esso, il ritorno avveniva tramite diversi return/throw, dimenticando di chiudere il file in un caso. Di conseguenza, il file handle aperto è fuoriuscito nel sistema.
Pro:
Contro:
Utilizzo di defer per garantire lo sblocco di mutex, liberazione del descrittore di file e ripristino del flag di progresso:
func criticalSection() { lock() defer { unlock() } // ... lavoro ... }
Pro:
Contro: