Siempre usaremos dentro del cierre en Swift

En WWDC 2014 sesión 403 Intermedio Swift y transcripción , hubo la siguiente diapositiva

enter image description here

El orador dijo en ese caso, si no usamos [unowned self] allí, será una pérdida de memoria. ¿Significa que siempre debemos usar [unowned self] dentro del cierre?

En la línea 64 de ViewController.swift de la aplicación Swift Weather , no utilizo [unowned self] . Pero actualizo la UI usando algunos @IBOutlet como self.temperature y self.loadingIndicator . Puede estar bien porque todos los @IBOutlet s que he definido son weak . Pero, para mayor seguridad, ¿deberíamos usar siempre [unowned self] ?

 class TempNotifier { var onChange: (Int) -> Void = {_ in } var currentTemp = 72 init() { onChange = { [unowned self] temp in self.currentTemp = temp } } } 

No, definitivamente hay momentos en los que no te gustaría usar [unowned self] . En ocasiones, desea que el cierre se capture a sí mismo para asegurarse de que aún está presente cuando se cierre el cierre.

Ejemplo: hacer una solicitud de red asincrónica

Si realiza una solicitud de red asincrónica , desea que el cierre se retenga self cuando finaliza la solicitud. Es posible que ese objeto haya sido desasignado, pero aún desea poder procesar el final de la solicitud.

Cuándo usar unowned self o weak self

El único momento en el que realmente desea usar [unowned self] o [weak self] es cuando crea un ciclo de referencia fuerte . Un ciclo de referencia fuerte es cuando hay un bucle de propiedad donde los objetos terminan siendo dueños el uno del otro (tal vez a través de un tercero) y por lo tanto nunca serán desasignados porque ambos se aseguran mutuamente.

En el caso específico de un cierre, solo necesita darse cuenta de que cualquier variable a la que se haga referencia dentro de ella, es “propiedad” del cierre. Mientras el cierre esté cerca, se garantiza que esos objetos estarán alrededor. La única manera de detener esa propiedad es hacer el [unowned self] o el [weak self] . Entonces, si una clase posee un cierre, y ese cierre captura una referencia fuerte a esa clase, entonces usted tiene un fuerte ciclo de referencia entre el cierre y la clase. Esto también incluye si la clase posee algo que posee el cierre.

Específicamente en el ejemplo del video

En el ejemplo de la diapositiva, TempNotifier posee el cierre a través de la variable de miembro onChange . Si no se declaraban a self como unowned , el cierre también sería self creando un fuerte ciclo de referencia.

Diferencia entre unowned y weak

La diferencia entre unowned y weak es que el weak se declara como opcional, mientras que el unowned no se declara. Al declararlo weak puedes manejar el caso de que podría estar nil dentro del cierre en algún momento. Si intenta acceder a una variable unowned que resulta ser nula, bloqueará todo el progtwig. Así que solo use unowned cuando esté seguro de que la variable siempre estará presente mientras el cierre esté alrededor

Actualización 11/2016

Escribí un artículo sobre esto extendiendo esta respuesta (mirando en SIL para entender qué hace ARC), compruébalo aquí .

Respuesta original

Las respuestas anteriores en realidad no dan reglas claras sobre cuándo usar una sobre la otra y por qué, así que déjame agregar algunas cosas.

La discusión sin dueño o débil se reduce a una cuestión de vida de la variable y el cierre que la hace referencia.

veloz débil vs sin dueño

Escenarios

Puedes tener dos escenarios posibles:

  1. El cierre tiene el mismo tiempo de vida de la variable, por lo que el cierre será alcanzable solo hasta que la variable sea alcanzable . La variable y el cierre tienen la misma duración. En este caso, debe declarar la referencia como no poseída . Un ejemplo común es el [unowned self] utilizado en muchos ejemplos de cierres pequeños que hacen algo en el contexto de sus padres y que al no estar referenciados en ningún otro lugar no sobreviven a sus padres.

  2. La vida útil del cierre es independiente de la de la variable, el cierre aún podría referenciarse cuando la variable ya no sea alcanzable. En este caso, debe declarar la referencia como débil y verificar que no sea nula antes de usarla (no fuerce desenvolver). Un ejemplo común de esto es el [weak delegate] que puede ver en algunos ejemplos de cierre que hace referencia a un objeto delegado completamente no relacionado (a lo largo de la vida).

Uso actual

Entonces, ¿cuál / debería usar la mayoría de las veces?

Citando a Joe Groff desde twitter :

Unowned es más rápido y permite la inmutabilidad y la no opción.

Si no necesitas algo débil, no lo uses.

Aquí encontrará más información sobre el funcionamiento interno sin dueño * .

* Por lo general también se conoce como no propietario (seguro) para indicar que se realizan comprobaciones de tiempo de ejecución (que provocan un locking por referencias no válidas) antes de acceder a la referencia sin propietario.

Si el yo puede ser nulo en el uso de cierre [yo débil] .

Si el yo nunca será nulo en el uso de cierre [yo sin dueño] .

La documentación de Apple Swift tiene una gran sección con imágenes que explican la diferencia entre usar cierres fuertes , débiles y sin dueño :

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

Aquí hay citas shinys de Apple Developer Forums describen detalles deliciosos:

unowned vs unowned(safe) vs unowned(unsafe)

unowned(safe) es una referencia no propietaria que afirma en el acceso que el objeto aún está activo. ¡Es como una referencia opcional débil implícitamente desenvuelta con x! cada vez que se accede. unowned(unsafe) es como __unsafe_unretained en ARC: no es una referencia de propiedad, pero no hay verificación en tiempo de ejecución de que el objeto aún esté activo en el acceso, por lo que las referencias colgantes llegarán a la memoria basura. unowned es siempre un sinónimo de unowned(safe) actualmente, pero la intención es que se optimice para las unowned(unsafe) en -Ofast cuando las comprobaciones del tiempo de ejecución están deshabilitadas.

unowned contra weak

unowned realmente usa una implementación mucho más simple que weak . Los objetos Swift nativos llevan dos recuentos de referencia, y las referencias unowned propietario superan el recuento de referencias sin propietario en lugar del recuento fuerte de referencias . El objeto se desinicializa cuando su recuento de referencia fuerte llega a cero, pero en realidad no se desasigna hasta que el recuento de referencia sin propietario también llegue a cero. Esto hace que la memoria se mantenga un poco más larga cuando hay referencias sin propietario, pero eso no suele ser un problema cuando se usa unowned porque los objetos relacionados deben tener tiempos de vida casi iguales de todos modos, y es mucho más simple y más bajo que el implementación basada en la tabla lateral utilizada para poner a cero las referencias débiles.

Actualización: en el moderno Swift, el weak usa internamente el mismo mecanismo que el que posee . Entonces, esta comparación es incorrecta porque compara Objective-C weak con Swift no unonwed .

Razones

¿Cuál es el propósito de mantener viva la memoria después de que las referencias lleguen a 0? ¿Qué sucede si el código intenta hacer algo con el objeto utilizando una referencia sin dueño después de que se desinicializa?

La memoria se mantiene viva de modo que sus recuentos de retención todavía están disponibles. De esta forma, cuando alguien intenta retener una referencia fuerte al objeto no propietario, el tiempo de ejecución puede verificar que el recuento de referencias fuertes sea mayor que cero para garantizar que sea seguro retener el objeto.

¿Qué sucede con las referencias propietarias o no controladas que posee el objeto? ¿Su vida está desacoplada del objeto cuando se desinicializa o su memoria también se conserva hasta que el objeto se desasigna después de que se libera la última referencia sin propietario?

Todos los recursos que posee el objeto se liberan tan pronto como se libera la última referencia fuerte del objeto y se ejecuta su deinit. Las referencias sin propietario solo mantienen viva la memoria, además del encabezado con los recuentos de referencia, su contenido es basura.

Emocionado, ¿eh?

Pensé que agregaría algunos ejemplos concretos específicamente para un controlador de vista. Muchas de las explicaciones, no solo aquí en Stack Overflow, son realmente buenas, pero trabajo mejor con ejemplos del mundo real (@drewag tuvo un buen comienzo en esto):

  • Si tiene un cierre para manejar una respuesta de una red, use weak , porque son de larga duración. El controlador de vista podría cerrarse antes de que la solicitud se complete, por lo que ya no apunta a un objeto válido cuando se invoca el cierre.
  • Si tiene cierre que maneja un evento en un botón. Esto puede unowned tener unowned porque tan pronto como el controlador de vista se va, el botón y cualquier otro elemento al que pueda hacer referencia desde self desaparece al mismo tiempo. El bloque de cierre también desaparecerá al mismo tiempo.

     class MyViewController: UIViewController { @IBOutlet weak var myButton: UIButton! let networkManager = NetworkManager() let buttonPressClosure: () -> Void // closure must be held in this class. override func viewDidLoad() { // use unowned here buttonPressClosure = { [unowned self] in self.changeDisplayViewMode() // won't happen after vc closes. } // use weak here networkManager.fetch(query: query) { [weak self] (results, error) in self?.updateUI() // could be called any time after vc closes } } @IBAction func buttonPress(self: Any) { buttonPressClosure() } // rest of class below. } 

Aquí hay algunas buenas respuestas. Pero los cambios recientes en la forma en que Swift implementa las referencias débiles deberían cambiar las decisiones de autodefensa de uno mismo y de sí mismo débil. Anteriormente, si necesitabas el mejor rendimiento con el yo sin dueño, era superior al yo débil, siempre y cuando pudieras estar seguro de que el yo nunca sería nulo, porque acceder al yo sin dueño es mucho más rápido que acceder al yo débil.

Pero Mike Ash ha documentado cómo Swift ha actualizado la implementación de vars débiles para usar tablas auxiliares y cómo esto mejora sustancialmente el auto rendimiento débil.

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

Ahora que no hay una penalización de rendimiento significativa para el yo débil, creo que debemos usarlo de forma predeterminada en el futuro. El beneficio del yo débil es que es opcional, lo que hace que sea mucho más fácil escribir un código más correcto, básicamente es la razón por la que Swift es un lenguaje tan bueno. Puede pensar que sabe qué situaciones son seguras para el uso del yo sin dueño, pero mi experiencia al revisar muchos otros códigos de desarrolladores es, la mayoría no lo hace. He solucionado muchos lockings en los que el autor sin dueño fue desasignado, generalmente en situaciones en las que un hilo de fondo se completa después de que un controlador es desasignado.

Los errores y fallas son las partes de la progtwigción que más tiempo consumen, más dolorosas y más costosas. Haz tu mejor esfuerzo para escribir el código correcto y evitarlo. Recomiendo que sea una norma que nunca se formen desenvueltos opcionales y que nunca se use el yo sin dueño en lugar del yo débil. No perderá nada que le falte a los momentos en que la fuerza desenvolviéndose y el ser sin dueño en realidad son seguros. Pero ganarás mucho al eliminar lockings y errores difíciles de encontrar y eliminar.

De acuerdo con Apple-doc

  • Las referencias débiles son siempre de tipo opcional y se vuelven nulas automáticamente cuando la instancia a la que hacen referencia es desasignada.

  • Si la referencia capturada nunca se volverá nula, siempre se debe capturar como una referencia sin propietario, en lugar de una referencia débil.

Ejemplo –

  // if my response can nil use [weak self] resource.request().onComplete { [weak self] response in guard let strongSelf = self else { return } let model = strongSelf.updateModel(response) strongSelf.updateUI(model) } // Only use [unowned self] unowned if guarantees that response never nil resource.request().onComplete { [unowned self] response in let model = self.updateModel(response) self.updateUI(model) }