Come gestire correttamente l'auto debole in blocchi rapidi con argomenti


151

Nel mio TextViewTableViewCell, ho una variabile per tenere traccia di un blocco e un metodo di configurazione in cui il blocco viene passato e assegnato.
Ecco la mia TextViewTableViewCellclasse:

//
//  TextViewTableViewCell.swift
//

import UIKit

class TextViewTableViewCell: UITableViewCell, UITextViewDelegate {

    @IBOutlet var textView : UITextView

    var onTextViewEditClosure : ((text : String) -> Void)?

    func configure(#text: String?, onTextEdit : ((text : String) -> Void)) {
        onTextViewEditClosure = onTextEdit
        textView.delegate = self
        textView.text = text
    }

    // #pragma mark - Text View Delegate

    func textViewDidEndEditing(textView: UITextView!) {
        if onTextViewEditClosure {
            onTextViewEditClosure!(text: textView.text)
        }
    }
}

Quando uso il metodo configure nel mio cellForRowAtIndexPathmetodo, come posso usare correttamente il sé debole nel blocco in cui passo.
Ecco cosa ho senza il sé debole:

let myCell = tableView.dequeueReusableCellWithIdentifier(textViewCellIdenfitier) as TextViewTableViewCell
myCell.configure(text: body, onTextEdit: {(text: String) in
   // THIS SELF NEEDS TO BE WEAK  
   self.body = text
})
cell = bodyCell

AGGIORNAMENTO : Ho ottenuto quanto segue per funzionare utilizzando [weak self]:

let myCell = tableView.dequeueReusableCellWithIdentifier(textViewCellIdenfitier) as TextViewTableViewCell
myCell.configure(text: body, onTextEdit: {[weak self] (text: String) in
        if let strongSelf = self {
             strongSelf.body = text
        }
})
cell = myCell

Quando lo faccio [unowned self]anziché [weak self]estrarre la ifdichiarazione, l'app si arresta in modo anomalo. Qualche idea su come dovrebbe funzionare [unowned self]?


Potresti selezionare una risposta come risposta corretta di seguito? Nota anche che con l'ignoto non avrai bisogno di rafforzare te stesso nella tua chiusura. Qui Unown è meglio che debole perché il ciclo di vita della cella e del controller di visualizzazione sono collegati.
ikuramedia,

1
Mi rendo conto che [sé sconosciuto] è l'opzione migliore, ma la mia app si arresta in modo anomalo quando la utilizzo. Mi piacerebbe vedere un esempio di codice che lo utilizza per chiudere la risposta.
NatashaTheRobot,

1
Dai documenti: "Come i riferimenti deboli, un riferimento non proprietario non mantiene una presa forte sull'istanza a cui fa riferimento. A differenza di un riferimento debole, tuttavia, si presume che un riferimento non noto abbia sempre un valore." Se l'app si arresta in modo anomalo, è probabilmente perché non riconosciuto viene applicato a un valore nullo in fase di esecuzione.
Bill Patterson,

Probabilmente è meglio pubblicizzare qui una dichiarazione di guardia piuttosto che lasciare che si leghi a strongSelf.
Dico

@NatashaTheRobot, Che sintassi è [sé debole]? sembra un messaggio che passa nell'obiettivo C. Puoi per favore aggiungere un po 'di più sulla sintassi nella domanda per favore.
Vignesh,

Risposte:


178

Se il potrebbe essere nullo nella chiusura usa [sé debole] .

Se il non sarà mai nullo nella chiusura, usa [sé sconosciuto] .

Se si arresta in modo anomalo quando si utilizza [un sé sconosciuto] , immagino che il sé sia ​​nullo a un certo punto in quella chiusura, motivo per cui invece si è dovuto andare con [sé debole] .

Mi è davvero piaciuta l'intera sezione del manuale sull'uso di chiusure forti , deboli e sconosciute :

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

Nota: ho usato il termine chiusura anziché blocco, che è il nuovo termine Swift:

Differenza tra blocco (obiettivo C) e chiusura (rapido) in iOS


7
Apple ha definito i blocchi "chiusure" nel loro primo documento per un'estensione del linguaggio C. (I blocchi o le chiusure sono inizialmente un'estensione di C. Solo MM è correlato all'Obiettivo-C.) Anche io preferisco anche il termine "chiusura", poiché "blocchi" in C è correlato molto spesso a istruzioni composte, esso è una specie di errore in entrambe le lingue, perché si chiama chiusura anche se non si chiude su un oggetto (variabile o costante).
Amin Negm-Awad,

1
ha risposto molto bene :)
iDevAmit

1
Suggerirei di non usare mai unowned. Non vale la pena rischiare di provocare il crash dell'app.
Kyle Redfearn,

32

Metti [unowned self]prima (text: String)...nella tua chiusura. Questo si chiama un elenco di acquisizione e inserisce le istruzioni di proprietà sui simboli acquisiti nella chiusura.


2
Grazie per averlo nominato, volevo saperlo!
rob5408,

3
Non penso che questa risposta sia utile. [sé sconosciuto] andrà in crash se il sé diventa nullo durante l'esecuzione della chiusura
Yunus Nedim Mehel

3
non vi è assolutamente alcun motivo di utilizzare non noti, tranne (1) in situazioni estremamente insolite, per le prestazioni (questo è assolutamente irrilevante qui e nel 99,999% della programmazione) e (2) come questione di applicazione dello stile. L'affermazione "Dovresti sempre usare deboli, mai sconosciuti" è molto ragionevole.
Fattie

29

** MODIFICATO per Swift 4.2:

Come ha commentato @Koen, swift 4.2 consente:

guard let self = self else {
   return // Could not get a strong reference for self :`(
}

// Now self is a strong reference
self.doSomething()

PS: Dato che sto avendo alcuni voti positivi, vorrei raccomandare la lettura sulla fuga dalle chiusure .

EDITED: Come ha commentato @ tim-vermeulen, Chris Lattner ha detto venerdì 22 gennaio 19:51:29 CST 2016, questo trucco non dovrebbe essere usato da solo, quindi per favore non usarlo. Controlla le informazioni sulle chiusure non in fuga e la risposta dell'elenco di acquisizione da @gbk. **

Per coloro che usano [sé debole] nell'elenco di acquisizione, nota che il sé potrebbe essere nullo, quindi la prima cosa che faccio è verificarlo con un'istruzione guard

guard let `self` = self else {
   return
}
self.doSomething()

Se ti stai chiedendo quali sono le virgolette, selfè un trucco da usare se stessi all'interno della chiusura senza dover cambiare il nome in questo , debole Se stesso o altro.


2
`self` è un esempio di shadowing, un articolo a riguardo può essere trovato qui arsenkin.com/swift-closure-without-ugly-strongSelf.html
Cullen SUN

2
Tendo a chiamare il "sé" locale "strongSelf" per assicurarmi che non sia confuso con il sé predefinito ed è più facile individuare se hai custodito un forte riferimento personale.
Justin Stanley,

1
Questo non dovrebbe essere usato, in quanto è un bug del compilatore: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/…
Tim Vermeulen,

1
Penso che il commento di Chris Lattner nel link sopra sia solo quello di non nominare la variabile come self(nei backtick). Assegnalo a qualcos'altro come nonOptionalSelf e andrà bene.
OutOnAWeekend

1
Al giorno d'oggi (swift 4.2) { [weak self] in guard let self = self else { return }può essere utilizzato senza backtick, ed è attualmente supportato: github.com/apple/swift-evolution/blob/master/proposals/…
Koen.

26

Usa l' elenco di acquisizione

Definizione di un elenco di acquisizione

Ogni elemento in un elenco di acquisizione è un'associazione della parola chiave debole o non definita con un riferimento a un'istanza di classe (come self) o una variabile inizializzata con un valore (come delegate = self.delegate!). Questi accoppiamenti sono scritti all'interno di una coppia di parentesi quadre, separati da virgole.

Posizionare l'elenco di acquisizione prima dell'elenco dei parametri di una chiusura e restituire il tipo se sono forniti:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here 
} 

Se una chiusura non specifica un elenco di parametri o un tipo restituito perché possono essere dedotti dal contesto, posizionare l'elenco di acquisizione all'inizio della chiusura, seguito dalla parola chiave in:

lazy var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // closure body goes here
}

spiegazioni aggiuntive


3
Hai usato sconosciuto per "sé", il che significa che sai che "sé" non sarà nullo quando accedi ad esso. Quindi hai usato forzare lo scartamento su "self.delegate" (il che significa anche che sai per certo che non sarà nulla) per assegnarlo a un var debole. Se sai con certezza che "autodelegazione" non sarà nulla, perché non utilizzare un non proprietario nel "delegato" anziché debole?
Roni Leshes,

26

EDIT: riferimento a una soluzione aggiornata di LightMan

Vedi la soluzione di LightMan . Fino ad ora stavo usando:

input.action = { [weak self] value in
    guard let this = self else { return }
    this.someCall(value) // 'this' isn't nil
}

O:

input.action = { [weak self] value in
    self?.someCall(value) // call is done if self isn't nil
}

Di solito non è necessario specificare il tipo di parametro se inferito.

Puoi omettere del tutto il parametro se non ce ne sono o se ti riferisci ad esso come $0nella chiusura:

input.action = { [weak self] in
    self?.someCall($0) // call is done if self isn't nil
}

Solo per completezza; se si passa la chiusura a una funzione e il parametro non lo è @escaping, non è necessario un weak self:

[1,2,3,4,5].forEach { self.someCall($0) }

9

A partire dal rapido 4.2 🔸 possiamo fare:

_ = { [weak self] value in
    guard let self = self else { return }
    print(self) //👈 will never be nil
}()

Altri hanno soluzioni simili, ma "questo" è C ++ IMHO. "strongSelf" è una convenzione di Apple e chiunque guardi il tuo codice saprà cosa sta succedendo.
David H,

1
@ David H IMO la frase strongSelfspiega esplicitamente il significato delle variabili / effetto laterale che è bello se il codice è di natura più lunga. apprezzo la tua opinione però, non sapevo che c ++ usasse questo fraseggio.
eonista,

3
A partire da Swift 4.2 è possibile utilizzare guard let self = self else { return }per scartare [weak self]: github.com/apple/swift-evolution/blob/master/proposals/…
Amer Hukic

@AmerHukic 👌.
eonista,


3

È possibile utilizzare [sé debole] o [sé sconosciuto] nell'elenco di acquisizione prima dei parametri del blocco. L'elenco di acquisizione è una sintassi opzionale.

[unowned self]funziona bene qui perché la cella non sarà mai nulla. Altrimenti puoi usare[weak self]


1
la cellula non è sé, non è nella classe cellulare, probabilmente è su un viewcontroller ...
Juan Boero,

0

Se stai andando in crash di quanto probabilmente hai bisogno di [sé debole]

La mia ipotesi è che il blocco che stai creando sia in qualche modo ancora cablato.

Crea un preparForReuse e prova a cancellare il blocco onTextViewEditClosure al suo interno.

func prepareForResuse() {
   onTextViewEditClosure = nil
   textView.delegate = nil
}

Vedi se questo impedisce l'incidente. (È solo una supposizione).


0

Chiusura e forti cicli di riferimento [Informazioni]

Come sapete, la chiusura di Swift può catturare l'istanza. Significa che puoi usare selfall'interno di una chiusura. Soprattutto escaping closure[Informazioni] può creare un strong reference cyclequale. A proposito, devi usare esplicitamente selfdentro escaping closure.

La chiusura rapida ha Capture Listfunzionalità che consentono di evitare tale situazione e interrompere un ciclo di riferimento perché non hanno un forte riferimento all'istanza acquisita. L'elemento Capture List è una coppia di weak/ unownede un riferimento a classe o variabile.

Per esempio

class A {
    private var completionHandler: (() -> Void)!
    private var completionHandler2: ((String) -> Bool)!

    func nonescapingClosure(completionHandler: () -> Void) {
        print("Hello World")
    }

    func escapingClosure(completionHandler: @escaping () -> Void) {
        self.completionHandler = completionHandler
    }

    func escapingClosureWithPArameter(completionHandler: @escaping (String) -> Bool) {
        self.completionHandler2 = completionHandler
    }
}

class B {
    var variable = "Var"

    func foo() {
        let a = A()

        //nonescapingClosure
        a.nonescapingClosure {
            variable = "nonescapingClosure"
        }

        //escapingClosure
        //strong reference cycle
        a.escapingClosure {
            self.variable = "escapingClosure"
        }

        //Capture List - [weak self]
        a.escapingClosure {[weak self] in
            self?.variable = "escapingClosure"
        }

        //Capture List - [unowned self]
        a.escapingClosure {[unowned self] in
            self.variable = "escapingClosure"
        }

        //escapingClosureWithPArameter
        a.escapingClosureWithPArameter { [weak self] (str) -> Bool in
            self?.variable = "escapingClosureWithPArameter"
            return true
        }
    }
}
  • weak- più preferibile, usalo quando è possibile
  • unowned - usalo quando sei sicuro che la durata del proprietario dell'istanza sia maggiore della chiusura
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.