tables realtime example arquitectura ios swift firebase swift3 firebase-database firebase-realtime-database

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 de BananaPeelingResult a un BananaStore
  • El BananaStore luego toma el Banana apropiado de su interior buscando peelingResult.bananaID
  • Habiendo encontrado un objeto con banana.bananaID == peelingResult.bananaID , establece banana.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 , incluido self 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 ...