example - realtime database firebase ios
El cierre no puede capturar implícitamente un parámetro automático mutante (5)
Estoy usando Firebase para observar el evento y luego configurar una imagen dentro del controlador de finalización
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")!
}
})
Sin embargo, recibo este error
El cierre no puede capturar implícitamente un parámetro automático mutante
No estoy seguro de qué se trata este error y la búsqueda de soluciones no ha ayudado
Solución de sincronización
Si necesita mutar un tipo de valor (
struct
) en un cierre, eso solo puede funcionar sincrónicamente, pero no para llamadas asíncronas, si lo escribe así:
struct Banana {
var isPeeled = false
mutating func peel() {
var result = self
SomeService.synchronousClosure { foo in
result.isPeeled = foo.peelingSuccess
}
self = result
}
}
De lo contrario, no puede capturar un "yo mutante" con tipos de valor, excepto al proporcionar una copia mutable (por lo tanto,
var
).
¿Por qué no Async?
La razón por la que esto no funciona en contextos asíncronos es: aún puede mutar el
result
sin un error del compilador, pero no puede asignar el resultado mutado de nuevo a
self
.
Aún así, no habrá ningún error, pero
self
nunca cambiará porque el método (
peel()
) se cierra antes de que se envíe el cierre.
Para evitar esto, puede intentar cambiar su código para cambiar la llamada asíncrona a ejecución sincrónica esperando que termine. Si bien es técnicamente posible, esto probablemente anula el propósito de la API asíncrona con la que está interactuando, y será mejor que cambie su enfoque.
Cambiar la
struct
a
class
es una opción técnicamente sólida, pero no aborda el problema real.
En nuestro ejemplo, ahora siendo una
class Banana
, su propiedad se puede cambiar asincrónicamente quién sabe cuándo.
Eso causará problemas porque es difícil de entender.
Es mejor escribir un controlador de API fuera del modelo en sí mismo y al finalizar la búsqueda, ejecutar y cambiar el objeto del modelo.
Sin más contexto, es difícil dar un ejemplo adecuado.
(Supongo que este es el código del modelo porque
self.img
está mutado en el código del OP).
Agregar objetos "anticorrupción asíncronos" puede ayudar
Estoy pensando en algo entre las líneas de esto:
-
un
BananaNetworkRequestHandler
ejecuta solicitudes de forma asincrónica y luego informa el resultado resultante deBananaPeelingResult
a unBananaStore
-
El
BananaStore
luego toma elBanana
apropiado de su interior buscandopeelingResult.bananaID
-
Habiendo encontrado un objeto con
banana.bananaID == peelingResult.bananaID
, establecebanana.isPeeled = peelingResult.isPeeled
, - finalmente reemplazando el objeto original con la instancia mutada.
Verá, a partir de la búsqueda para encontrar una solución simple, puede involucrarse bastante fácilmente, especialmente si los cambios necesarios incluyen cambiar la arquitectura de la aplicación.
¡Puedes probar esto! Espero poder ayudarte.
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
}
Otra solución es capturar explícitamente uno mismo (ya que en mi caso, estaba en una función de mutación de una extensión de protocolo, por lo que no podía especificar fácilmente que este fuera un tipo de referencia).
Entonces, en lugar de esto:
functionWithClosure(completion: { _ in
self.property = newValue
})
Tengo esto:
var closureSelf = self
functionWithClosure(completion: { _ in
closureSelf.property = newValue
})
Lo que parece haber silenciado la advertencia.
Tenga en cuenta que esto no funciona para los tipos de valor, por lo que si self es un tipo de valor, debe usar un contenedor de tipo de referencia para que esta solución funcione.
Si alguien está tropezando con esta página (desde la búsqueda) y está definiendo un
protocol
/
protocol extension
, entonces podría ser útil si declara su
protocol
como
un enlace de clase
.
Me gusta esto:
protocol MyProtocol: class
{
...
}
La versión corta
El tipo que posee su llamada a
FirebaseRef.observeSingleEvent(of:with:)
es muy probablemente un tipo de valor (una
struct
?), En cuyo caso un contexto mutante puede no capturarse explícitamente en un cierre
@escaping
.
La solución simple es actualizar su tipo de propiedad a una referencia una vez (
class
).
La versión más larga
El
observeSingleEvent(of:with:)
de Firebase
se declara de la siguiente manera
func observeSingleEvent(of eventType: FIRDataEventType, with block: @escaping (FIRDataSnapshot) -> Void)
El cierre del
block
está marcado con el
@escaping
parámetro
@escaping
, lo que significa que puede escapar del cuerpo de su función e incluso de la vida útil de
self
(en su contexto).
Usando este conocimiento, construimos un ejemplo más mínimo que podemos analizar:
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 */
}
}
Ahora, el mensaje de error se vuelve más revelador, y pasamos a la siguiente propuesta de evolución que se implementó en Swift 3:
Afirmando [el énfasis es mío]:
Capturar un parámetro
inout
, incluidoself
en un método de mutación , se convierte en un error en un literal de cierre evitable, a menos que la captura se haga explícita (y por lo tanto inmutable).
Ahora, este es un punto clave.
Para un tipo de
valor
(por ejemplo,
struct
), que creo que también es el caso del tipo que posee la llamada a
observeSingleEvent(...)
en su ejemplo, tal captura explícita no es posible, afaik (ya que estamos trabajando con un tipo de valor, y no de referencia).
La solución más simple para este problema sería hacer que el tipo propietario de
observeSingleEvent(...)
un tipo de referencia, por ejemplo, una
class
, en lugar de una
struct
:
class Foo {
init() {}
private func bar(with block: @escaping () -> ()) { block() }
func bax() {
bar { print(self) }
}
}
Solo tenga en cuenta que esto se capturará a
self
con una referencia fuerte;
dependiendo de su contexto (no he usado Firebase yo mismo, así que no lo sabría), es posible que desee capturarse explícitamente débilmente, por ejemplo
FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...