Storia della questione:
L'escape analysis è un termine dell'ottimizzazione dei compilatori e della gestione della memoria. In Swift è importante a causa dell'uso attivo dei closure e dell'ARC. È legato ai concetti di escaping e non-escaping closure, che definiscono se i closure possono uscire dal contesto di visibilità della funzione.
Problema:
La definizione errata del tipo di closure porta a errori di possesso della memoria, perdite, acquisizioni inattese di variabili e cali delle prestazioni. È necessario comprendere chiaramente quando un closure "scappa" (escapes) e quando no, e contrassegnarli correttamente.
Soluzione:
Swift richiede di contrassegnare esplicitamente i closure escaping con l'attributo @escaping. I closure non-escaping possono essere acquisiti solo all'interno della funzione, possiedono automaticamente una gestione della memoria più efficiente e possono utilizzare le variabili acquisite in modo più sicuro.
Esempio di differenza:
// closure non-escaping (più veloce, non viene memorizzato dopo la chiamata) func performSync(block: () -> Void) { block() } // closure escaping (può essere memorizzato e eseguito in seguito) var storedCompletion: (() -> Void)? func performAsync(block: @escaping () -> Void) { storedCompletion = block }
Caratteristiche chiave:
È possibile cambiare un closure definito come non-escaping in escaping senza modificare il codice sorgente della funzione?
No. L'attributo @escaping deve essere specificato esplicitamente nella firma della funzione. Se il closure all'interno della funzione viene passato al di fuori di essa, è necessario solo un closure escaping, altrimenti il compilatore restituirà un errore.
È sicuro passare self all'interno di un closure escaping senza acquisizione weak/unowned?
No. Un closure escaping può potenzialmente creare un retain cycle se acquisisce self con un riferimento forte. È necessario gestire esplicitamente l'acquisizione:
someAsync { [weak self] in self?.doSomething() }
Un closure escaping è sempre globale (rimane in memoria fino alla fine del programma)?
No. Il suo ciclo vitale dipende dall'area di memorizzazione. Se un closure è memorizzato solo temporalmente o la proprietà viene azzerata, verrà deallocato non appena la proprietà perde il suo possesso. Diventano globali solo i closure che hanno riferimenti a oggetti globali o li memorizzano in variabili globali.
All'interno di un metodo di classe memorizzano un closure senza @escaping (errore del compilatore), poi lo correggono, dimenticano di proteggersi dal retain cycle — l'applicazione ha una perdita di memoria.
Pro:
Contro:
Lo sviluppatore controlla sempre dove è necessario un closure escaping, acquisisce self come weak/unowned, evita perdite, lavora in modo sicuro con la memoria.
Pro:
Contro: