¿Debemos usar siempre [uno mismo unowned] dentro del cierre en Swift


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

introduzca la descripción de la imagen aquí

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

En la línea 64 de ViewController.swift de la aplicación Swift Weather , no uso [unowned self]. Pero actualizo la interfaz de usuario usando algunos @IBOutlet s como self.temperature y self.loadingIndicator. Puede estar bien porque todos @IBOutlet s I definido son weak. Pero por 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
    }
  }
}
Author: Ryan Heitner, 2014-06-20

7 answers

No, definitivamente hay momentos en los que no querrías usar [unowned self]. A veces, desea que el cierre se capture a sí mismo para asegurarse de que todavía esté disponible en el momento en que se llame al cierre.

Ejemplo: Hacer una solicitud de red asíncrona

Si está haciendo una solicitud de red asíncrona, desea que el cierre se mantenga self para cuando finalice la solicitud. Ese objeto puede haber sido desasignado de otra manera, pero aún así desea ser capaz de manejar el acabado 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 crearía un ciclo de referencia fuerte . Un ciclo de referencia fuerte es cuando hay un bucle de propiedad donde los objetos terminan siendo propietarios unos de otros (tal vez a través de un tercero) y, por lo tanto, nunca se desasignarán porque ambos se aseguran de que el otro se quede.

En el caso específico de un cierre, simplemente necesidad de darse cuenta de que cualquier variable que se hace referencia dentro de ella, obtiene "propiedad" por el cierre. Mientras el cierre esté alrededor, esos objetos están garantizados para estar alrededor. La única manera de detener esa propiedad, es hacer el [unowned self] o [weak self]. Así que si una clase posee un cierre y ese cierre captura una fuerte referencia a esa clase, entonces tienes 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 miembro onChange. Si no declararan self como unowned, el cierre también haría que self creara un ciclo de referencia sólido.

Diferencia entre unowned y weak

La diferencia entre unowned y weak es que weak se declara como Opcional, mientras que unowned no lo es. Al declararlo weak se llega a manejar el caso de que podría ser nulo dentro del cierre en algún momento. Si intenta acceder a una variable unowned que resulta ser nil, bloqueará todo el programa. Así que solo use unowned cuando esté positivo, esa variable siempre estará alrededor mientras que el cierre está alrededor

 709
Author: drewag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-23 15:34:29

Actualización 11/2016

Escribí un artículo sobre esto extendiendo esta respuesta (buscando en SIL para entender lo que hace ARC), échale un vistazo aquí.

Respuesta original

Las respuestas anteriores realmente no dan reglas sencillas sobre cuándo usar una sobre la otra y por qué, así que permítanme agregar algunas cosas.

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

rápido débil vs sin dueño

Escenarios

Puedes tener dos escenarios posibles:

  1. El cierre tiene la misma vida útil 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 vida útil. En este caso debe declarar la referencia como unowned. Un ejemplo común es el [unowned self] utilizado en muchos ejemplos de pequeños cierres que hacen algo en el contexto de su padre y que no se hace referencia 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 todavía podría ser referenciado cuando la variable ya no es accesible. En este caso, debe declarar la referencia como débil y verificar que no es nil antes de usarla (no forzar el desenvolvimiento). Un ejemplo común de esto es el [weak delegate] que puede ver en algunos ejemplos de cierre haciendo referencia a un objeto delegado (de por vida).

Uso real

Entonces, ¿qué usarás/deberías usar realmente la mayoría de las veces?

Citando a Joe Groff de twitter :

Unowned es más rápido y permite la inmutabilidad y la nonoptionality.

Si no necesitas débil, no lo uses.

Aquíencontrará más información sobre el funcionamiento interno de unowned * .

* Por lo general, también se conoce como unowned (seguro) para indique que las comprobaciones de tiempo de ejecución (que conducen a un bloqueo de referencias no válidas) se realizan antes de acceder a la referencia no usada.

 139
Author: Umberto Raimondi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-19 06:37:59

Si self podría ser nulo en el cierre, use [weak self] .

Si self nunca será nulo en el cierre use [unowned self] .

La documentación de Apple Swift tiene una gran sección con imágenes que explican la diferencia entre usar strong, débil, y no poseído en cierres:

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

 58
Author: TenaciousJay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-29 15:47:56

Aquí hay citas brillantes de Foros de Desarrolladores de Apple describe deliciosos detalles:

unowned vs unowned(safe) vs unowned(unsafe)

unowned(safe) es una referencia no propietaria que afirma en el acceso que el objeto sigue vivo. Es una especie de referencia opcional débil eso se desenvuelve implícitamente con x! cada vez que se accede. unowned(unsafe) es como __unsafe_unretained en ARC-es un no propietario referencia, pero no hay comprobación de tiempo de ejecución de que el objeto sigue vivo en el acceso, así las referencias colgantes alcanzarán la memoria basura. {[0] } es siempre un sinónimo de unowned(safe) actualmente, pero el la intención es que se optimizará a unowned(unsafe) en -Ofast se compila cuando las comprobaciones de tiempo de ejecución están deshabilitadas.

unowned vs weak

unowned en realidad utiliza una implementación mucho más simple que weak. Los objetos Swift nativos tienen dos recuentos de referencia, y unowned referencias bump the unowned reference count en lugar de la strong reference count . El objeto es deinitializado cuando su strong referencia count alcanza cero, pero en realidad no está desasignado hasta el unowned reference count también llega a cero. Esto hace que la memoria sea se aferró un poco más cuando hay referencias no conocidas, pero que no suele ser un problema cuando se usa unowned porque los los objetos deben tener vidas casi iguales de todos modos, y es mucho más simple y menor sobrecarga que la implementación basada en la mesa auxiliar utilizada para cero referencias débiles.

Actualización: En Swift modernoweak internamente utiliza el mismo mecanismo que unowned hace. Así que esta comparación es incorrecta porque compara Objective-C weak con Swift unonwed.

Razones

¿Cuál es el propósito de mantener la memoria viva después de poseer referencias llegar a 0? Qué sucede si el código intenta hacer algo con ¿el objeto que usa una referencia no usada después de que se desinicializa?

El la memoria se mantiene viva para que sus recuentos de retención estén todavía disponibles. De esta manera, cuando alguien intenta retener una fuerte referencia a la unowned objeto, el tiempo de ejecución puede comprobar que el fuerte recuento de referencia es mayor que cero con el fin de garantizar que es seguro para retener el objeto.

¿Qué sucede con las referencias poseídas o no poseídas por el objeto? Es su tiempo de vida desacoplado del objeto cuando se desinicializa o es su memoria también retenida hasta que el objeto es desasignado después ¿se publica la última referencia no registrada?

Todos los recursos propiedad del objeto se liberan tan pronto como el objeto se libera la última referencia fuerte, y se ejecuta su deinit. Sin dueño las referencias solo mantienen viva la memoria-aparte de la cabecera con el la referencia cuenta, su contenido es basura.

[21] Emocionado, ¿eh?
 44
Author: Valentin Shergin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:50

Pensé en agregar 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 solicitud de red, use weak, porque son de larga duración. El controlador de vista podría cerrarse antes la solicitud se completa de manera que self ya no apunta a un objeto válido cuando el cierre es called.
  • Si tiene un cierre que maneja un evento en un botón. Esto puede ser unowned porque tan pronto como el controlador de vista desaparece, el botón y cualquier otro elemento desde el que pueda estar haciendo referencia 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.
     }
    
 40
Author: possen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-10 23:53:06

Hay algunas grandes respuestas aquí. Sin embargo, los cambios recientes en la forma en que Swift implementa referencias débiles deberían cambiar las decisiones de auto-uso débiles de todos frente a las decisiones de auto-uso no propias. Anteriormente, si necesitabas el mejor rendimiento usando uno mismo no poseído era superior al uno mismo débil, siempre y cuando pudieras estar seguro de que el uno mismo nunca sería nulo, porque acceder al uno mismo no poseído es mucho más rápido que acceder al uno mismo débil.

Pero Mike Ash ha documentado cómo Swift ha actualizado la implementación de var débiles para utilizar mesas laterales y cómo esto mejora sustancialmente el rendimiento débil de uno mismo.

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 deberíamos usarlo de forma predeterminada en el futuro. El beneficio de weak self es que es opcional, lo que hace que sea mucho más fácil escribir código más correcto, es básicamente la razón por la que Swift es un gran lenguaje. Usted puede pensar que usted sabe que las situaciones son seguras para el uso de unowned self, pero mi experiencia revisando un montón de código de otros desarrolladores es, la mayoría no. He corregido un montón de bloqueos donde unowned self fue desasignado, por lo general en situaciones en las que se completa un subproceso en segundo plano después de que un controlador es desasignado.

Los errores y bloqueos son las partes más costosas, dolorosas y lentas de la programación. Haga todo lo posible para escribir el código correcto y evitarlos. Recomiendo hacer una regla para nunca forzar el desenvolver opcionales y nunca use el yo no poseído en lugar del yo débil. Usted no perderá nada que falta la fuerza de los tiempos que desenvuelve y uno mismo unowned realmente es seguro. Pero usted ganará mucho al eliminar los bloqueos y errores difíciles de encontrar y depurar.

 21
Author: Randy Hill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-26 22:31:48

De acuerdo con Apple-doc

  • Las referencias débiles son siempre de tipo opcional, y automáticamente se convierten en nil cuando la instancia a la que hacen referencia está desasignada.

  • Si la referencia capturada nunca se convertirá en nula, siempre debe capturarse como una referencia no registrada, 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)
     }
 2
Author: Jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-09 10:52:07