2015-10-21 13 views
12

Dove definire i riferimenti acquisiti per le chiusure annidate in Swift?Posizionamento corretto dell'elenco di cattura nelle chiusure annidate in swift

prendere questo codice come un esempio:

import Foundation 

class ExampleDataSource { 
    var content: Any? 
    func loadContent() { 
     ContentLoader.loadContentFromSource() { [weak self] loadedContent in 
      // completion handler called on background thread 
      dispatch_async(dispatch_get_main_queue()) { [weak self] in 
       self?.content = loadedContent 
      } 
     } 
    } 
} 

class ContentLoader { 
    class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) { 
     /* 
     Load content from web asynchronously, 
     and call completion handler on background thread. 
     */ 
    } 
} 

In questo esempio, [weak self] viene utilizzato in entrambe le chiusure finali, tuttavia il compilatore è perfettamente felice se tralascio [weak self] da uno dei due chiusure finali.

In modo che mi lascia 3 opzioni per definire la mia lista di cattura:

  1. definiscono cattura su ogni chiusura nidificato che porta al riferimento
  2. definiscono catturate solo il primo di chiusura.
  3. definisce le acquisizioni solo sulla chiusura più annidata che utilizza effettivamente il riferimento.

La mia domanda è:

Se so che il mio ExampleDataSource potrebbe essere nil ad un certo punto, qual è l'opzione migliore per andare con?

risposta

14

È importante notare che GCD dispatch_async NON causerà un ciclo di conservazione. In altre parole, quando il blocco ha terminato l'esecuzione, GCD non manterrà alcun riferimento fatto all'interno del blocco.

Lo stesso non vale per i riferimenti forti tra le classi o per i riferimenti forti all'interno di una chiusura assegnata a una proprietà di un'istanza. Apple Documentation

Detto questo, in questo esempio, la risposta corretta è l'opzione 2, per definire le acquisizioni solo sulla prima chiusura.

A scopo di verifica ho modificato il codice un po ':

class ExampleDataSource { 
    init() { 
     print("init()") 
    } 
    deinit { 
     print("deinit") 
    } 
    var content: Any? 
    func loadContent() { 
     print("loadContent()") 
     ContentLoader.loadContentFromSource() { [weak self] loadedContent in 
      dispatch_async(dispatch_get_main_queue()) { 
       print("loadedContent") 
       self?.content = loadedContent 
      } 
     } 
    } 
} 

class ContentLoader { 
    class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) { 
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) { 
      sleep(5) // thread will hang for 5 seconds 
      completion(loadedContent: "some data") 
     } 
    } 
} 

prima cosa creare, var myDataSource: ExampleDataSource? = ExampleDataSource().

Quindi corro myDataSource.loadContent().

Prima che il gestore di completamento abbia la possibilità di eseguire, ho impostato myDataSource = nil, rimuovendo tutti i riferimenti ad esso.

La console di debug indica che un riferimento a sé non è stata mantenuta:

init() 
loadContent() 
deinit 
loadedContent 

Sembra che abbiamo trovato la nostra risposta! Ma per l'amor di completamento, cerchiamo di testare le alternative ...

Se [weak self] viene invece catturato solo la chiusura più finale interna, GCD manterrà ExampleDataSource fino a quando il blocco è terminato l'esecuzione, il che spiega perché il debug di sarebbe invece simile questo:

init() 
loadContent() 
loadedContent 
deinit 

la stessa cosa accadrà se nessuna lista di cattura è inclusa e non abbiamo mai scartato opzionalmente self, anche se il compilatore, non cerca di mettere in guardia voi!

Anche se non è tecnicamente corretto includere le acquisizioni [weak self] in tutte le chiusure finali, ciò diminuisce la leggibilità del codice e non sembra molto "Swift-like".

Problemi correlati