In Swift utilizzeremo sempre [un self self] all'interno della chiusura


467

Nella sessione 403 Intermediate Swift e trascrizione della WWDC 2014 , c'era la seguente diapositiva

inserisci qui la descrizione dell'immagine

L'oratore ha detto in quel caso, se non usiamo [unowned self]lì, sarà una perdita di memoria. Significa che dovremmo sempre usare la [unowned self]chiusura interna?

Nella riga 64 di ViewController.swift dell'app Swift Weather , non uso [unowned self]. Ma aggiorno l'interfaccia utente usando alcuni @IBOutlets come self.temperaturee self.loadingIndicator. Potrebbe essere OK perché sono tutti @IBOutletdefiniti weak. Ma per sicurezza, dovremmo sempre usare [unowned self]?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}

il collegamento all'immagine è interrotto
Daniel Gomez Rico,

@ DanielG.R. Grazie, posso vederlo. i.stack.imgur.com/Jd9Co.png
Jake Lin il

2
A meno che non mi sbagli, l'esempio fornito nella diapositiva non è corretto: onChangedovrebbe essere una [weak self]chiusura, poiché è una proprietà pubblica (internamente, ma comunque), quindi un altro oggetto potrebbe ottenere e archiviare la chiusura, mantenendo l'oggetto TempNotifier intorno (indefinitamente se l'oggetto che utilizza non ha lasciato andare la onChangechiusura fino a quando non vede TempNotifierscomparire, tramite il suo debole riferimento a TempNotifier) . Se var onChange …fosse private var onChange …allora [unowned self]sarebbe corretto. Non ne sono sicuro al 100%; qualcuno mi corregga per favore se sbaglio.
Slipp D. Thompson,

@Jake Lin `var onChange: (Int) -> Void = {}` le parentesi graffe rappresentano una chiusura vuota? come nel definire un array vuoto con []? Non riesco a trovare la spiegazione nei documenti di Apple.
bibscy,

@bibscy sì, {}è la chiusura vuota (l'istanza della chiusura) come impostazione predefinita (non fa nulla), (Int) -> Voidè la definizione di chiusura.
Jake Lin,

Risposte:


871

No, ci sono sicuramente momenti in cui non vorresti usare [unowned self]. A volte vuoi che la chiusura catturi il sé per assicurarti che sia ancora in circolazione quando viene chiamata la chiusura.

Esempio: esecuzione di una richiesta di rete asincrona

Se si stanno facendo una richiesta asincrona della rete si fa vuole la chiusura di conservare selfper quando termina la richiesta. In caso contrario, l'oggetto potrebbe essere stato deallocato ma si desidera comunque essere in grado di gestire la finitura della richiesta.

Quando usare unowned selfoweak self

L'unica volta in cui vuoi davvero usare [unowned self]o [weak self]è quando crei un forte ciclo di riferimento . Un forte ciclo di riferimento è quando esiste un ciclo di proprietà in cui gli oggetti finiscono per possedere l'un l'altro (forse attraverso una terza parte) e quindi non saranno mai deallocati perché entrambi si assicurano che si attacchino l'un l'altro.

Nel caso specifico di una chiusura, è sufficiente rendersi conto che qualsiasi variabile a cui viene fatto riferimento al suo interno viene "posseduta" dalla chiusura. Fintanto che la chiusura è in giro, questi oggetti sono garantiti per essere in giro. L'unico modo per fermare quella proprietà è fare il [unowned self]o [weak self]. Quindi se una classe possiede una chiusura e quella chiusura cattura un forte riferimento a quella classe, allora hai un forte ciclo di riferimento tra la chiusura e la classe. Ciò include anche se la classe possiede qualcosa che possiede la chiusura.

In particolare nell'esempio del video

Nell'esempio sulla diapositiva, TempNotifierpossiede la chiusura tramite la onChangevariabile membro. Se non lo dichiarassero selfcome unowned, la chiusura avrebbe anche selfcreato un forte ciclo di riferimento.

Differenza tra unownedeweak

La differenza tra unownede weakè che weakè dichiarata come opzionale mentre unownednon lo è. Dichiarandolo weaksi arriva a gestire il caso che potrebbe essere nulla all'interno della chiusura ad un certo punto. Se si tenta di accedere a una unownedvariabile che risulta essere nulla, si bloccherà l'intero programma. Quindi usa solo unownedquando sei sicuro che la variabile sarà sempre presente mentre la chiusura è intorno


1
Ciao. Bella risposta. Sto lottando per capire l'io sconosciuto. Un motivo per usare il debole Se stessi semplicemente "se stessi diventa un optional", non è abbastanza per me. Perché dovrei specificamente voler usare 'auto di proprietà di nessuno' stackoverflow.com/questions/32936264/...

19
@robdashnash, Il vantaggio di usare un sé sconosciuto è che non è necessario scartare un codice opzionale che può essere un codice non necessario se si è certi per progettazione, che non sarà mai zero. In definitiva, un sé sconosciuto viene usato per brevità e forse anche come suggerimento per i futuri sviluppatori che non ti aspetti mai un valore nullo.
Drewag,

77
Un caso da utilizzare [weak self]in una richiesta di rete asincrona è in un controller di visualizzazione in cui tale richiesta viene utilizzata per popolare la vista. Se l'utente esegue il backout, non è più necessario popolare la vista, né è necessario un riferimento al controller della vista.
David James,

1
weaki riferimenti sono impostati anche su nilquando l'oggetto è deallocato. unownedi riferimenti non lo sono.
BergQuester,

1
Sono un po 'confuso. unownedè usato per non-Optionalmentre weakè usato per Optionalcosì il nostro selfè Optionalo non-optional?
Muhammad Nayab,

193

Aggiornamento 11/2016

Ho scritto un articolo su questa estensione di questa risposta (esaminando SIL per capire cosa fa ARC), controlla qui .

Risposta originale

Le risposte precedenti in realtà non danno regole chiare su quando usare l'una sull'altra e perché, quindi lasciatemi aggiungere alcune cose.

La discussione sconosciuta o debole si riduce a una questione di durata della variabile e alla chiusura che la fa riferimento.

rapido debole vs sconosciuto

scenari

Puoi avere due possibili scenari:

  1. La chiusura ha la stessa durata della variabile, quindi la chiusura sarà raggiungibile solo fino a quando la variabile non sarà raggiungibile . La variabile e la chiusura hanno la stessa durata. In questo caso è necessario dichiarare il riferimento come non posseduto . Un esempio comune è quello [unowned self]usato in molti esempi di piccole chiusure che fanno qualcosa nel contesto del loro genitore e che a cui non si fa riferimento altrove non sopravvivono ai loro genitori.

  2. La durata della chiusura è indipendente da quella della variabile, la chiusura potrebbe ancora essere referenziata quando la variabile non è più raggiungibile. In questo caso dovresti dichiarare il riferimento come debole e verificare che non sia nullo prima di usarlo (non forzare lo scartamento). Un esempio comune di ciò è quello [weak delegate]che puoi vedere in alcuni esempi di chiusura che fanno riferimento a un oggetto delegato completamente non correlato (per tutta la vita).

Utilizzo effettivo

Quindi, quale sarà / dovresti effettivamente utilizzare la maggior parte delle volte?

Citando Joe Groff da Twitter :

Unown è più veloce e consente l'immutabilità e la non opzione.

Se non hai bisogno di debole, non usarlo.

Troverete di più su senza proprietario *funzionamento interno qui .

* Di solito indicato anche come non noto (sicuro) per indicare che i controlli di runtime (che portano a un arresto anomalo per riferimenti non validi) vengono eseguiti prima di accedere al riferimento non noto.


26
Sono stanco di sentire la spiegazione del pappagallo "usa la settimana se il sé potrebbe essere nullo, usa il sconosciuto quando non può mai essere nullo". Ok, l'abbiamo capito - l'ho sentito un milione di volte! Questa risposta in realtà scava più in profondità su quando il sé può essere nullo in un inglese semplice, il che risponde direttamente alla domanda del PO. Grazie per questa grande spiegazione !!
TruMan1,

Grazie @ TruMan1, sto davvero scrivendo un post su questo che finirà presto sul mio blog, aggiornerò la risposta con un link.
Umberto Raimondi,

1
Bella risposta, molto pratica. Sono ispirato a passare alcuni dei miei deboli sensibili alle prestazioni a non posseduti ora.
originale

"La durata della chiusura è indipendente da quella della variabile" Hai un refuso qui?
Miele

1
Se una chiusura ha sempre la stessa durata dell'oggetto genitore, il conteggio dei riferimenti non verrebbe comunque curato quando l'oggetto viene distrutto? Perché non puoi semplicemente usare il "sé" in questa situazione invece di preoccuparti di non conosciuti o deboli?
LegendLength

105

Ho pensato di aggiungere alcuni esempi concreti specifici per un controller di visualizzazione. Molte delle spiegazioni, non solo qui su Stack Overflow, sono davvero buone, ma lavoro meglio con esempi del mondo reale (@drewag ha avuto un buon inizio su questo):

  • Se si dispone di una chiusura per gestire una risposta da una rete weak, utilizzare le richieste , poiché sono di lunga durata. Il controller di visualizzazione potrebbe chiudersi prima del completamento della richiesta, quindi selfnon punta più a un oggetto valido quando viene chiamata la chiusura.
  • Se hai una chiusura che gestisce un evento su un pulsante. Ciò può essere unowneddovuto al fatto che non appena il controller di visualizzazione si spegne, il pulsante e qualsiasi altro oggetto a cui fa riferimento selfscompare contemporaneamente. Anche il blocco di chiusura andrà via contemporaneamente.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }

17
Questo ha bisogno di più voti. Due esempi concreti che mostrano come una chiusura a pulsante non esisterà al di fuori della durata di vita del controller di visualizzazione e pertanto può utilizzare le proprietà sconosciute, ma la maggior parte delle chiamate di rete che aggiornano l'interfaccia utente deve essere debole.
Tim Fuqua,

2
Quindi, solo per chiarire, usiamo sempre non conosciuti o deboli quando chiamiamo sé in un blocco di chiusura? O c'è un momento in cui non chiameremo debole / sconosciuto? In tal caso, potresti fornire un esempio anche per quello?
luke

Grazie mille.
Shawn Baek,

1
Questo mi ha dato una comprensione più profonda di [sé debole] e [sé sconosciuto] Grazie mille @possen!
Tommy

Questo è fantastico cosa succede se ho un'animazione basata sull'interazione dell'utente, ma ci vuole un po 'per terminare. E quindi l'utente passa a un altro viewController. Immagino che in quel caso dovrei ancora usare weakpiuttosto che unownedgiusto?
Miele


50

Ecco citazioni brillanti dai forum degli sviluppatori Apple che descrivono deliziosi dettagli:

unownedvs unowned(safe)vsunowned(unsafe)

unowned(safe)è un riferimento non proprietario che afferma sull'accesso che l'oggetto è ancora vivo. È un po 'come un riferimento facoltativo debole che è implicitamente da scartare x!ogni volta che si accede. unowned(unsafe)è come __unsafe_unretainedin ARC: è un riferimento non proprietario, ma non esiste alcun controllo di runtime che l'oggetto sia ancora vivo all'accesso, quindi i riferimenti penzolanti raggiungeranno la memoria dell'immondizia. unownedè sempre sinonimo di unowned(safe)attualmente, ma l'intento è che sarà ottimizzato unowned(unsafe)nelle -Ofast build quando i controlli di runtime sono disabilitati.

unowned vs weak

unownedin realtà utilizza un'implementazione molto più semplice di weak. Gli oggetti Swift nativi portano due conteggi di riferimento e i unowned riferimenti aumentano il conteggio di riferimento non proprietario invece del conteggio di riferimento forte . L'oggetto viene deinizializzato quando il suo conteggio di riferimento forte raggiunge lo zero, ma in realtà non viene deallocato fino a quando anche il conteggio di riferimento non proprietario raggiunge lo zero. Questo fa sì che la memoria rimanga trattenuta leggermente più a lungo quando ci sono riferimenti non proprietari, ma di solito non è un problemaunowned viene utilizzato perché gli oggetti correlati dovrebbero avere una durata pressoché uguale, ed è molto più semplice ed economico rispetto all'implementazione basata sulla tabella laterale utilizzata per azzerare i riferimenti deboli.

Aggiornamento: In Swift moderna weakutilizza internamente lo stesso meccanismo unownedfa . Quindi questo confronto non è corretto perché confronta Objective-C weakcon Swift unonwed.

Motivi

Qual è lo scopo di mantenere viva la memoria dopo aver posseduto i riferimenti raggiungere lo 0? Cosa succede se il codice tenta di fare qualcosa con l'oggetto usando un riferimento non proprietario dopo che è stato deinizializzato?

La memoria viene mantenuta in vita in modo che i suoi conteggi di conservazione siano ancora disponibili. In questo modo, quando qualcuno tenta di conservare un riferimento forte all'oggetto non posseduto, il runtime può verificare che il conteggio dei riferimenti forti sia maggiore di zero per garantire che sia sicuro conservare l'oggetto.

Cosa succede al possesso o ai riferimenti non posseduti contenuti nell'oggetto? La loro durata è disaccoppiata dall'oggetto quando viene deinizializzato o viene conservata la loro memoria fino a quando l'oggetto non viene deallocato dopo il rilascio dell'ultimo riferimento sconosciuto?

Tutte le risorse di proprietà dell'oggetto vengono rilasciate non appena viene rilasciato l'ultimo riferimento sicuro dell'oggetto e viene eseguita la sua deinit. I riferimenti noti mantengono viva la memoria, a parte l'intestazione con i conteggi dei riferimenti, il suo contenuto è spazzatura.

Eccitato, eh?


38

Ci sono alcune grandi risposte qui. Ma le recenti modifiche al modo in cui Swift implementa i riferimenti deboli dovrebbero cambiare il sé debole di tutti rispetto alle decisioni di autouso non note. In precedenza, se avevi bisogno delle migliori prestazioni usando il sé sconosciuto era superiore al sé debole, fintanto che potevi essere sicuro che il sé non sarebbe mai nullo, perché l'accesso al sé sconosciuto è molto più veloce dell'accesso al sé debole.

Ma Mike Ash ha documentato in che modo Swift ha aggiornato l'implementazione dei var deboli per utilizzare le tabelle laterali e in che modo questo migliora sostanzialmente le auto prestazioni deboli.

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

Ora che non c'è una significativa penalità di prestazione per il sé debole, credo che dovremmo default usarlo in futuro. Il vantaggio di un io debole è che è un optional, il che rende molto più semplice scrivere codice più corretto, fondamentalmente è il motivo per cui Swift è un linguaggio così eccezionale. Potresti pensare di sapere quali situazioni sono sicure per l'uso di un sé sconosciuto, ma la mia esperienza di revisione di molti altri codici di sviluppatori è, la maggior parte no. Ho risolto molti arresti anomali in cui era stato deallocato l'io sconosciuto, di solito in situazioni in cui un thread in background viene completato dopo che un controller è stato allocato.

Bug e arresti anomali sono le parti della programmazione più dispendiose in termini di tempo, dolorose e costose. Fai del tuo meglio per scrivere il codice corretto ed evitarli. Consiglio di fare una regola per non forzare mai gli opzionali da scartare e non usare mai un sé sconosciuto invece di un sé debole. Non perderai nulla mancando i tempi che costringono a scartare e l'io sconosciuto in realtà sono al sicuro. Ma otterrai molto dall'eliminazione di errori e bug difficili da trovare e trovare.


Grazie per l'aggiornamento e Amen sull'ultimo paragrafo.
motto

1
Quindi, dopo le nuove modifiche, c'è mai stato un momento in cui weaknon può essere utilizzato al posto di un unowned?
Miele

4

Secondo Apple-doc

  • I riferimenti deboli sono sempre di tipo facoltativo e diventano automaticamente zero quando l'istanza a cui fanno riferimento viene deallocata.

  • Se il riferimento acquisito non diventerà mai nullo, dovrebbe sempre essere catturato come riferimento non proprietario, piuttosto che come riferimento debole

Esempio -

    // if my response can nil use  [weak self]
      resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else {
        return
      }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
     }

    // Only use [unowned self] unowned if guarantees that response never nil  
      resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
     }

0

Se nessuna delle precedenti ha senso:

tl; dr

Proprio come un implicitly unwrapped optional, Se puoi garantire che il riferimento non sarà nullo nel suo punto di utilizzo, usa non noto. In caso contrario, dovresti usare debole.

Spiegazione:

Ho recuperato quanto segue in: collegamento debole sconosciuto . Da quello che ho raccolto, il sé sconosciuto non può essere nullo, ma il sé debole può essere, e il sé sconosciuto può portare a puntatori penzolanti ... qualcosa di famigerato in Objective-C. Spero che sia d'aiuto

"UNOWNED I riferimenti deboli e sconosciuti si comportano in modo simile ma NON sono gli stessi."

I riferimenti noti, come i riferimenti deboli, non aumentano il conteggio di mantenimento dell'oggetto a cui si fa riferimento. Tuttavia, in Swift, un riferimento sconosciuto non ha il vantaggio di non essere un Opzionale . Ciò li rende più facili da gestire piuttosto che ricorrere all'uso dell'associazione opzionale. Questo non è diverso dagli Optionals implicitamente non confezionati. Inoltre, i riferimenti non noti sono azzeranti . Ciò significa che quando l'oggetto è deallocato, non azzera il puntatore. Ciò significa che l'uso di riferimenti non noti può, in alcuni casi, portare a puntatori penzolanti. Per voi secchioni là fuori che ricordano i giorni dell'Obiettivo-C come me, i riferimenti non noti si associano a riferimenti non sicuri.

È qui che diventa un po 'confuso.

Riferimenti deboli e non noti non aumentano entrambi i conteggi di conservazione.

Entrambi possono essere utilizzati per interrompere i cicli di mantenimento. Quindi quando li usiamo ?!

Secondo i documenti di Apple :

“Usa un riferimento debole ogni volta che è valido per quel riferimento a diventare nullo ad un certo punto durante la sua vita. Al contrario, utilizzare un riferimento non noto quando si sa che il riferimento non sarà mai zero dopo che è stato impostato durante l'inizializzazione. "


0
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

In caso di dubbi, [unowned self] utilizzare [weak self]

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.