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é
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
, incluantself
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 ...
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
).
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.)
Je pense à quelque chose parmi les lignes de ceci:
BananaNetworkRequestHandler
exécute les requêtes de manière asynchrone, puis renvoie le résultat BananaPeelingResult
à un BananaStore
BananaStore
prend ensuite le Banana
approprié de l'intérieur en cherchant peelingResult.bananaID
banana.bananaID == peelingResult.bananaID
, Il définit ensuite banana.isPeeled = peelingResult.isPeeled
,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.
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
{
...
}
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
}