ProgrammazioneMiddle/Senior iOS Developer

Qual è l'essenza dell'operatore defer in Swift e quali sono gli scenari principali della sua applicazione, le caratteristiche del lavoro con le chiusure e la gestione delle risorse?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della domanda

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.

Problema

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.

Soluzione

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:

  • Viene eseguito in qualsiasi uscita dall'ambito (return, throw, break),
  • È possibile dichiarare più defer consecutivi - vengono eseguiti in ordine inverso,
  • Molto utile per la gestione delle risorse, gestione degli errori, blocco/sblocco, ripristino di stati temporanei.

Domande insidiose.

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.

Errori comuni e anti-pattern

  • Utilizzare defer per operazioni asincrone (l'ultimo defer rilascia, mentre il codice asincrono non è ancora terminato),
  • Catturare implicitamente variabili esterne mutabili (condizioni di gara nella multithreading),
  • Dichiarare molti defer consecutivi - si perde la leggibilità e il debug.

Esempio dalla vita reale

Caso negativo

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:

  • Logica semplice e lineare

Contro:

  • Facile dimenticare di liberare la risorsa in ogni uscita
  • Memory leak/resource leak

Caso positivo

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:

  • Alta sicurezza della risorsa
  • Riduzione degli errori

Contro:

  • Ulteriore nidificazione dell'ambito con un gran numero di defer