Comment gérer correctement le moi faible dans les blocs Swift avec des arguments

151

Dans my TextViewTableViewCell, j'ai une variable pour garder une trace d'un bloc et une méthode de configuration où le bloc est passé et attribué.
Voici ma 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)
        }
    }
}

Quand j'utilise la méthode configure dans ma cellForRowAtIndexPathméthode, comment utiliser correctement le self faible dans le bloc que je passe.
Voici ce que j'ai sans le self faible:

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

MISE À JOUR : J'ai fait fonctionner les éléments suivants en utilisant [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

Lorsque je fais [unowned self]au lieu de [weak self]et que je retire la ifdéclaration, l'application se bloque. Des idées sur la façon dont cela devrait fonctionner [unowned self]?

NatashaTheRobot
la source
Pourriez-vous alors sélectionner une réponse ci-dessous comme réponse correcte? Notez également qu'avec une personne sans propriétaire, vous n'avez pas besoin de vous renforcer dans votre fermeture. Sans propriétaire est mieux que faible ici car le cycle de vie de votre cellule et de votre contrôleur de vue est lié.
ikuramedia
1
Je me rends compte que [moi-même] est la meilleure option, mais mon application plante lorsque je l'utilise. J'adorerais voir un exemple de code l'utiliser pour fermer la réponse.
NatashaTheRobot
1
Extrait de la documentation: "Comme les références faibles, une référence sans propriétaire ne conserve pas une forte emprise sur l'instance à laquelle elle fait référence. Cependant, contrairement à une référence faible, une référence sans propriétaire est supposée avoir toujours une valeur." Si votre application plante, elle est probablement parce que sans propriétaire est appliqué à une valeur nulle au moment de l'exécution.
Bill Patterson
Il est probablement préférable d'annoncer une déclaration de garde ici que de se lier à strongSelf. Juste en disant, c'est comme le candidat parfait :-D
Daniel Galasko
@NatashaTheRobot, Quelle syntaxe est [soi faible]?. ressemble à un message passant dans l'objectif C. Pouvez-vous s'il vous plaît ajouter un peu plus sur la syntaxe dans la question s'il vous plaît.
Vignesh le

Réponses:

178

Si le soi pouvait être nul dans la fermeture, utilisez [soi faible] .

Si le soi ne sera jamais nul dans la fermeture, utilisez [soi sans propriétaire] .

Si ça plante quand vous utilisez [soi sans propriétaire], je suppose que le soi est nul à un moment donné de cette fermeture, c'est pourquoi vous avez dû opter pour [le moi faible] à la place.

J'ai vraiment aimé toute la section du manuel sur l'utilisation de fermetures fortes , faibles et inconnues :

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

Remarque: j'ai utilisé le terme fermeture au lieu de bloc qui est le nouveau terme Swift:

Différence entre le bloc (Objectif C) et la fermeture (Swift) dans iOS

TenaciousJay
la source
7
Apple a appelé les blocs «fermetures» dans leur tout premier document pour une extension en langage C. (Les blocs ou fermetures sont une extension de C au tout début. Seul MM est lié à Objective-C.) Même moi, je préfère aussi le terme «fermeture», car les «blocs» en C sont très souvent liés aux instructions composées, il est une sorte de mal dans les deux langues, car cela s'appelle une fermeture même si elle ne se ferme pas sur un objet (variable ou constante).
Amin Negm-Awad
1
très bien répondu :)
iDevAmit
1
Je suggérerais de ne jamais utiliser unowned. Cela ne vaut pas le risque de faire planter votre application.
Kyle Redfearn
32

Mettez [unowned self]avant (text: String)...dans votre fermeture. Cela s'appelle une liste de capture et place des instructions de propriété sur les symboles capturés dans la fermeture.

Ikuramedia
la source
2
Merci de l'avoir nommé, je voulais le savoir!
rob5408
3
Je ne pense pas que cette réponse soit utile. [soi sans propriétaire] s'écroulera si le soi devient nul pendant l'exécution de la fermeture
Yunus Nedim Mehel
3
il n'y a absolument aucune raison d'utiliser sans propriétaire, autre que (1) dans des situations extrêmement inhabituelles, pour la performance (ceci est totalement hors de propos ici et dans 99,999% de la programmation) et (2) comme une question d'application de style. La déclaration «Vous devriez toujours utiliser faible, jamais sans propriétaire» est très raisonnable.
Fattie
29

** MODIFIÉ pour Swift 4.2:

Comme @Koen l'a commenté, swift 4.2 permet:

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

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

PS: Étant donné que j'ai des votes positifs, je voudrais recommander la lecture sur les fermetures .

EDITED: Comme @ tim-vermeulen l'a commenté, Chris Lattner l'a déclaré le vendredi 22 janvier à 19:51:29 CST 2016, cette astuce ne doit pas être utilisée sur soi-même, alors ne l'utilisez pas. Vérifiez les informations sur les fermetures qui ne s'échappent pas et la réponse de la liste de capture de @gbk. **

Pour ceux qui utilisent [self faible] dans la liste de capture, notez que self peut être nul, donc la première chose que je fais est de vérifier cela avec une déclaration de garde

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

Si vous vous demandez ce que sont les guillemets, selfc'est une astuce professionnelle pour utiliser self à l'intérieur de la fermeture sans avoir besoin de changer le nom en ceci , lowSelf ou autre.

Homme léger
la source
2
`self` est un exemple d'ombrage, un article à ce sujet peut être trouvé ici arsenkin.com/swift-closure-without-ugly-strongSelf.html
Cullen SUN
2
J'ai tendance à appeler le "self" local "strongSelf" pour m'assurer qu'il n'est pas confondu avec le self par défaut et qu'il est plus facile de repérer si vous avez gardé une forte référence personnelle.
Justin Stanley
1
Cela ne doit pas être utilisé, car il s'agit d'un bogue du compilateur: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/…
Tim Vermeulen
1
Je pense que le commentaire de Chris Lattner dans le lien ci-dessus est simplement de ne pas nommer la variable comme self(dans les backticks). Nommez-le autre chose comme nonOptionalSelf et tout ira bien.
OutOnAWeekend
1
De nos jours (swift 4.2) { [weak self] in guard let self = self else { return }peut être utilisé sans backticks, et est en fait supporté: github.com/apple/swift-evolution/blob/master/proposals
Koen.
26

Utiliser la liste de capture

Définition d'une liste de capture

Chaque élément dans une liste de capture est une association du mot-clé faible ou sans propriétaire avec une référence à une instance de classe (telle que self) ou une variable initialisée avec une valeur (telle que delegate = self.delegate!). Ces paires sont écrites dans une paire d'accolades, séparées par des virgules.

Placez la liste de capture avant la liste de paramètres d'une fermeture et le type de retour s'ils sont fournis:

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

Si une fermeture ne spécifie pas de liste de paramètres ou de type de retour car ils peuvent être déduits du contexte, placez la liste de capture au tout début de la fermeture, suivi du mot-clé in:

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

explications supplémentaires

gbk
la source
3
Vous avez utilisé «soi» sans propriétaire, ce qui signifie que vous savez avec certitude que «soi» ne sera pas nul lorsque vous y accédez. Ensuite, vous avez utilisé le déballage forcé sur "self.delegate" (ce qui signifie également que vous savez avec certitude qu'il ne sera pas nul) pour l'assigner à une variable faible. Si vous savez avec certitude que "self.delegate" ne sera pas nul, pourquoi ne pas utiliser sans propriétaire le "délégué" au lieu de faible?
Roni Leshes
26

EDIT: Référence à une solution mise à jour par LightMan

Voir la solution de LightMan . Jusqu'à présent, j'utilisais:

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

Ou:

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

En général, vous n'avez pas besoin de spécifier le type de paramètre s'il est déduit.

Vous pouvez omettre complètement le paramètre s'il n'y en a pas ou si vous vous y référez comme $0dans la fermeture:

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

Juste pour être complet; si vous passez la fermeture à une fonction et que le paramètre ne l'est pas @escaping, vous n'avez pas besoin d'un weak self:

[1,2,3,4,5].forEach { self.someCall($0) }
Ferran Maylinch
la source
9

Depuis Swift 4.2 🔸, nous pouvons faire:

_ = { [weak self] value in
    guard let self = self else { return }
    print(self) //👈 will never be nil
}()
éoniste
la source
D'autres ont des solutions similaires, mais "ceci" est C ++ IMHO. "strongSelf" est une convention Apple et quiconque jette un coup d'œil à votre code saura ce qui se passe.
David H
1
@ David H IMO la phrase strongSelfexplique explicitement les variables signification / effet secondaire, ce qui est bien si le code est de nature plus longue. apprécier votre avis cependant, ne savais pas que c ++ utilisé une telle formulation.
eonist
3
Depuis Swift 4.2, vous pouvez utiliser guard let self = self else { return }pour déballer[weak self] : github.com/apple/swift-evolution/blob/master/proposals
Amer Hukic
@AmerHukic 👌.
eonist
3

Vous pouvez utiliser [soi faible] ou [soi sans propriétaire] dans la liste de capture avant vos paramètres du bloc. La liste de capture est une syntaxe facultative.

[unowned self]fonctionne bien ici car la cellule ne sera jamais nulle. Sinon, vous pouvez utiliser[weak self]

Rufus
la source
1
la cellule n'est pas soi-même, il n'est pas sur la classe de cellule, il est probablement sur un viewcontroller ...
Juan Boero
0

Si vous vous écrasez, vous avez probablement besoin de [moi faible]

Je suppose que le bloc que vous créez est en quelque sorte toujours câblé.

Créez un prepareForReuse et essayez d'effacer le bloc onTextViewEditClosure à l'intérieur.

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

Voyez si cela empêche le crash. (C'est juste une supposition).

Michael Gray
la source
0

Clôture et cycles de référence solides [À propos]

Comme vous le savez, la fermeture de Swift peut capturer l'instance. Cela signifie que vous pouvez utiliser à l' selfintérieur d'une fermeture. Surtout escaping closure[About] peut créer un strong reference cyclequi. Au fait, vous devez explicitement utiliser selfinside escaping closure.

La fermeture rapide a une Capture Listfonction qui vous permet d'éviter une telle situation et de rompre un cycle de référence car vous n'avez pas de référence forte à l'instance capturée. L'élément Capture List est une paire de weak/ unownedet une référence à une classe ou une variable.

Par exemple

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- plus préférable, utilisez-le quand c'est possible
  • unowned - utilisez-le lorsque vous êtes sûr que la durée de vie du propriétaire de l'instance est supérieure à la fermeture
yoAlex5
la source