2014-10-26 18 views
5

Ho scoperto che le chiusure Swift non mantengono le variabili catturate a differenza delle mie aspettative.La chiusura di Swift conserva le variabili acquisite?

class AAA { 
} 
var a1 = AAA() as AAA?     // expects RC == 1 
var a2 = {()->AAA? in return a1 }  // expects RC == 2, retained by `Optional<AAA>` 
a1  = nil       // expects RC == 1 
a2()          // prints nil, ???? 

Sono molto confuso con questo, perché ho creduto alle variabili catturati verranno conservati per impostazione predefinita. Ma, se lo catturo esplicitamente usando la lista di cattura, viene conservato.

class AAA { 
} 
var a1 = AAA() as AAA? 
var a2 = { [a1]()->AAA? in return a1 } 
a1  = nil 
a2() // prints {AAA}, alive as expected. 

Ho riletto il manuale di Swift, ma non ho trovato la descrizione correlata. L'elenco di cattura viene utilizzato per impostare unowned in modo esplicito e sono ancora confuso. Qual è il comportamento corretto e perché succede?

risposta

5

Sì è documentato in Capturing Values:

Swift determina ciò che dovrebbe essere catturata da riferimento e quale dovrebbe essere copiato per valore. Non è necessario annotare quantità o runningTotal per dire che possono essere utilizzati all'interno della funzione incrementore nidificata. Swift gestisce anche tutta la gestione della memoria coinvolta nello smaltimento di runningTotal quando non è più necessario dalla funzione di incremento.

La regola è: se si fa riferimento a una variabile acquisita senza modificarla, viene acquisita per valore. Se invece lo modifichi, viene catturato per riferimento. Ovviamente, a meno che tu non lo sostituisca esplicitamente definendo un elenco di acquisizione.

Addendum Le dichiarazioni precedenti non sono corrette. Le acquisizioni sono fatte con riferimento indipendentemente dal fatto che siano modificate o meno all'interno della chiusura. Leggi il commento @newacct.

+1

Non vero. Ad esempio, 'func test() {var x = 42; sia f = {println (x)}; x = 43; f()}; test() 'stampa' 43', il che significa che la chiusura cattura la variabile per riferimento, anche se fa riferimento alla variabile senza modificarla. – newacct

+0

Grazie a @newacct. Anche se non dicendo esplicitamente quello che ho scritto, la documentazione mi ha fatto pensare che funzionasse in quel modo. – Antonio

+0

C'è un modo per forzare il compilatore a catturare per riferimento? Ho un caso in cui ho due chiusure ciascuna cercando di accedere a un valore condiviso. Una chiusura modifica il valore catturato e l'altro la legge solo. Tuttavia, quello che lo legge sta ricevendo una copia. Devo forzarlo a catturare per riferimento. –

1

Ho postato la stessa domanda su Forum degli sviluppatori Apple e c'era a discussion. Sebbene le persone non stiano parlando di contare i riferimenti molto, ma ho qualche idea. Ecco la mia conclusione:

  • Un valore viene sempre copiato quando è associato a un nuovo nome (esplicito). Indipendentemente da var o let.
  • Copiato in tipi di riferimento significa RC + 1. Perché sta copiando un puntatore forte come C++ shared_ptr<T>.
  • Chiusura (chiusure di chiusure) non cambia nulla perché non c'è un nuovo nome. È come se li usassi nello stesso stack. Questo è il significato dei mezzi per riferimento. Nulla è collegato a RC e non cambia RC perché è qualcosa di simile a riferimenti in C++.
  • L'elenco di cattura è un nome esplicito (let). Quindi causa la copia e rende RC + 1.
  • Quando la chiusura viene restituita da una funzione, potrebbe essere associata a un nome. In caso affermativo, RC + 1 a causa del nuovo collegamento a un nome.
  • RC-1 quando un valore (quindi un puntatore forte) non è associato al suo nome.
  • Il riferimento implicito a self è l'unica eccezione che rende il binding implicito del nome.

E ci sono molte ottimizzazioni senza infrangere queste regole, comunque quelle sono solo dettagli di implementazione.

1

L'esempio non ha senso @newacct. La variabile x è effettivamente modificata al di fuori del blocco. Swift è abbastanza intelligente da scoprire se si modifica una variabile all'interno o all'esterno della chiusura.

Come dice il documento:

Come un'ottimizzazione, Swift può invece acquisire e memorizzare una copia di un valore se tale valore non è mutato da o al di fuori di una chiusura.

Per quanto riguarda la posta domanda da @Eonil, nel primo frammento, hai un forte riferimento alla Optional<AAA> in chiusura. Tuttavia, quando si imposta da a1 a zero, ciò che si fa in realtà è la rimozione del valore di tipo AAA che è incluso nell'opzionale.

Quando si chiama la chiusura a2, si restituisce lo stesso opzionale senza valore all'interno, che è esattamente un mezzo nil. Pertanto, il mantenimento di un riferimento forte significa che la chiusura condivide lo stesso valore facoltativo di a1. Ciò non significa che questo opzionale non può essere impostato su zero.

Nel tuo secondo snippet di codice, hai catturato a1 nell'elenco di acquisizione. Ciò significa che copi lo a1 e il valore a1 all'interno della chiusura non ha nulla a che fare con il valore a1 al di fuori della chiusura. Quindi, anche se si imposta a1 su zero, ciò non influisce su ciò che si ottiene dalla chiusura.

Il mio inglese non è buono e spero di aver espresso chiaramente la mia opinione. Oppure puoi leggere this article, credo che ti aiuterà molto.

Problemi correlati