73 votes

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é

95voto

dfri Points 11222

La version courte

Le type de posséder votre appel à l' FirebaseRef.observeSingleEvent(of:with:) est probablement un type de valeur (un struct?), dans ce cas, une mutation du contexte peut ne pas explicitement capture self en @escaping fermeture.

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


La version longue

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

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

L' block fermeture est marqué avec l' @escaping attribut de paramètre, ce qui signifie qu'il peut quitter le corps de sa fonction, et même la durée de vie d' self (dans ce contexte). Grâce à cette connaissance, nous construisons un plus minime exemple 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 de plus en plus d'histoires, et nous nous tournons vers l'évolution suivante de la proposition a été mis en œuvre dans Swift 3:

En indiquant [c'est moi qui souligne]:

La capture d'une inout paramètre, y compris self en mutation méthode, devient une erreur dans un escapable fermeture littérale, à moins que le la capture est faite explicite (et donc immuable).

Maintenant, c'est un point clé. Pour une valeur de type (par exemple, struct), ce qui je crois est aussi le cas pour le type qui possède l'appel à l' observeSingleEvent(...) dans votre exemple, une telle explicite de capture n'est pas possible, autant que je sache (puisque nous travaillons avec un type de valeur, et non une référence à l'un).

La plus simple solution de ce problème serait de faire le type de propriétaire de la observeSingleEvent(...) d'un type de référence, par exemple, un class, plutôt qu'un struct:

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

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

Méfiez-vous cependant que cela permettra de capturer self par une référence forte; en fonction de votre contexte (je n'ai pas utilisé Firebase moi-même, donc je ne sais pas), vous pouvez explicitement capture self faiblement, par exemple

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

31voto

ctietze Points 128

Solution De Synchronisation

Si vous avez besoin de muter un type de la valeur (struct) par une fermeture, qui ne peut fonctionner de manière synchrone, mais pas pour les appels asynchrones, si vous l'écrire 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 une "mutation de soi" avec des types de valeur, sauf en fournissant une mutable (d'où l' var) de la copie.

Pourquoi ne pas Async?

La raison pour laquelle cela ne fonctionne pas dans async contextes: vous pouvez encore muter result sans erreur de compilation, mais vous ne pouvez pas affecter la mutation du résultat en self. Encore, il n'y aura pas d'erreur, mais self ne changera jamais parce que la méthode (peel()) sorties avant la fermeture est encore distribué.

Pour contourner cela, vous pouvez essayer de modifier votre code pour modifier l'appel asynchrone synchrone exécution en attente pour elle à la fin. Bien que techniquement possible, c'est probablement ce qui défait le but de l'API asynchrone vous interagissez avec, et vous feriez mieux de changer votre approche.

Évolution struct de class est techniquement son option, mais ne traite pas le problème réel. Dans notre exemple, il est tout à fait class Banana, sa propriété peut être modifiée de manière asynchrone qui-sait-quand. Qui pose des problèmes car il est difficile à comprendre. Vous êtes mieux de l'écriture d'un API du gestionnaire à l'extérieur du modèle lui-même et à fini de l'exécution de récupérer et de modifier le modèle objet. Sans plus de contexte, il est difficile de donner un exemple d'adaptation. (Je suppose que c'est le code du modèle, car self.img est muté dans le cas des OP de code.)

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

Je suis en train de réfléchir à quelque chose entre les lignes de ce:

  • un BananaNetworkRequestHandler exécute des demandes de manière asynchrone et puis les rapports résultant BananaPeelingResult retour à un BananaStore
  • L' BananaStore , puis prend le approprié Banana de son intérieur, en recherchant peelingResult.bananaID
  • Ayant trouvé un objet avec banana.bananaID == peelingResult.bananaID, il définit ensuite banana.isPeeled = peelingResult.isPeeled,
  • enfin, le remplacement de l'objet d'origine avec la mutation de l'instance.

Vous voyez, de la quête de trouver une solution simple, il peut devenir très impliqué facilement, surtout si les modifications nécessaires comprennent l'évolution de l'architecture de l'application.

15voto

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

 protocol MyProtocol: class
{
   ...
}
 

-18voto

Gujamin Points 1016

Une autre solution consiste à s'auto-capturer explicitement (puisque dans mon cas, j'étais dans une fonction de mutation d'une extension de protocole, je ne pouvais donc pas facilement spécifier qu'il s'agissait d'un type de référence).

Donc au lieu de cela:

 functionWithClosure(completion: { _ in
    self.property = newValue
})
 

J'ai ceci:

 var closureSelf = self
functionWithClosure(completion: { _ in
    closureSelf.property = newValue
})
 

Ce qui semble avoir fait taire l'avertissement.

Notez que cela ne fonctionne pas pour les types de valeur. Par conséquent, si self est un type de valeur, vous devez utiliser un wrapper de type de référence pour que cette solution fonctionne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X