web-dev-qa-db-fra.com

La fermeture ne peut pas implicitement capturer un paramètre auto en mutation

J'utilise Firebase pour observer un événement, puis je place une image dans le gestionnaire d'achèvement.

FirebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
        if let _ = snapshot.value as? NSNull {
            self.img = UIImage(named:"Some-image")!
        } else {
            self.img = UIImage(named: "some-other-image")!
        }
})

Cependant je reçois cette erreur

La fermeture ne peut pas implicitement capturer un paramètre auto en mutation

Je ne suis pas certain de la nature de cette erreur et la recherche de solutions n'a pas aidé

47
coding_999

La version courte

Le type possédant votre appel à FirebaseRef.observeSingleEvent(of:with:) est probablement un type de valeur (un struct?), Auquel cas un contexte de mutation ne peut pas capturer explicitement self dans un @escaping Fermeture.

La solution simple consiste à mettre à jour votre type de propriétaire avec une référence une fois (class).


La version la plus longue

La méthode observeSingleEvent(of:with:) de Firebase est déclarée comme suit

func observeSingleEvent(of eventType: FIRDataEventType, 
     with block: @escaping (FIRDataSnapshot) -> Void)

La fermeture de block est marquée avec l'attribut de paramètre @escaping, Ce qui signifie qu'elle peut échapper au corps de sa fonction et même à la durée de vie de self (dans votre contexte). En utilisant cette connaissance, nous construisons un exemple plus minimal que nous pouvons analyser:

struct Foo {
    private func bar(with block: @escaping () -> ()) { block() }

    mutating func bax() {
        bar { print(self) } // this closure may outlive 'self'
        /* error: closure cannot implicitly capture a 
                  mutating self parameter              */
    }
}

Maintenant, le message d'erreur devient plus éloquent et nous passons à la proposition d'évolution suivante mise en œuvre dans Swift 3:

Déclarant [c'est moi qui souligne]:

Capturer un paramètre inout, incluant self dans une méthode de mutation , devient une erreur dans un littéral de fermeture échappable, sauf si la capture est rendue explicite (et donc immuable).

Maintenant, c'est un point clé. Pour un type valeur (par exemple, struct), ce qui, à mon avis, est également valable pour le type propriétaire de l'appel à observeSingleEvent(...) dans votre exemple, tel que une capture explicite n'est pas possible, autant que je sache (puisque nous travaillons avec un type valeur, et non avec un type de référence).

La solution la plus simple à ce problème consisterait à faire du type possédant le observeSingleEvent(...) un type de référence, par ex. un class plutôt qu'un struct:

class Foo {
    init() {}
    private func bar(with block: @escaping () -> ()) { block() }

    func bax() {
        bar { print(self) }
    }
}

Attention, cela capturera self par une référence forte; En fonction de votre contexte (je n'ai pas utilisé Firebase moi-même, je ne le saurais pas), vous voudrez peut-être explicitement capturer self faiblement, par exemple.

FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...
63
dfri

Solution de synchronisation

Si vous devez muter un type de valeur (struct) dans une fermeture, cela ne peut fonctionner que de manière synchrone, mais pas pour les appels asynchrones, si vous l'écrivez comme ceci:

struct Banana {
    var isPeeled = false
    mutating func peel() {
        var result =  self

        SomeService.synchronousClosure { foo in
            result.isPeeled = foo.peelingSuccess
        }

        self = result
    }
}

Vous ne pouvez pas capturer autrement un "moi en mutation" avec des types valeur, sauf en fournissant une copie mutable (donc var).

Pourquoi pas async?

La raison pour laquelle cela ne fonctionne pas dans les contextes asynchrones est la suivante: vous pouvez toujours muter result sans erreur du compilateur, mais vous ne pouvez pas affecter le résultat muté à self. Néanmoins, il n'y aura pas d'erreur, mais self ne changera jamais car la méthode (peel()) se ferme avant même que la fermeture ne soit distribuée.

Pour contourner ce problème, vous pouvez essayer de modifier votre code afin de modifier l'appel asynchrone en exécution synchrone en attendant la fin de celui-ci. Bien que techniquement possible, cela va probablement à l'encontre de l'objectif de l'API asynchrone avec laquelle vous interagissez et il vaudrait mieux changer votre approche.

Changer struct en class est une option techniquement judicieuse, mais ne résout pas le vrai problème. Dans notre exemple, étant maintenant un class Banana, Sa propriété peut être modifiée de manière asynchrone par qui-sait-quand. Cela causera des problèmes parce que c'est difficile à comprendre. Il est préférable d'écrire un gestionnaire d'API en dehors du modèle lui-même et, une fois l'exécution terminée, extraire et modifier l'objet du modèle. Sans plus de contexte, il est difficile de donner un exemple approprié. (Je suppose que c'est du code de modèle car self.img Est muté dans le code de l'OP.)

L'ajout d'objets "async anti-corruption" peut aider

Je pense à quelque chose parmi les lignes de ceci:

  • a BananaNetworkRequestHandler exécute les requêtes de manière asynchrone, puis renvoie le résultat BananaPeelingResult à un BananaStore
  • Le BananaStore prend ensuite le Banana approprié de l'intérieur en cherchant peelingResult.bananaID
  • Après avoir trouvé un objet avec banana.bananaID == peelingResult.bananaID, Il définit ensuite banana.isPeeled = peelingResult.isPeeled,
  • en remplaçant finalement l'objet d'origine par l'instance mutée.

Vous voyez, dans la quête d'une solution simple, il peut être assez compliqué, surtout si les modifications nécessaires incluent la modification de l'architecture de l'application.

16
ctietze

Si quelqu'un tombe sur cette page (de la recherche) et que vous définissez un protocol/protocol extension, alors cela pourrait aider si vous déclariez votre protocol comme lié à la classe. Comme ça:

protocol MyProtocol: class
{
   ...
}
12

Vous pouvez essayer ça! J'espère vous aider.

struct Mutating {
    var name = "Sen Wang"

    mutating func changeName(com : @escaping () -> Void) {

        var muating = self {
            didSet {
                print("didSet")
                self = muating
            }
        }

        execute {
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15, execute: {
                muating.name = "Wang Sen"
                com()
            })
        }
    }

    func execute(with closure: @escaping () -> ()) { closure() }
}


var m = Mutating()
print(m.name) /// Sen Wang

m.changeName {
    print(m.name) /// Wang Sen
}
0
Must_Save_Jane