¿Siempre usaremos [yo sin propiedad] dentro del cierre en Swift


467

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

ingrese la descripción de la imagen aquí

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

En la línea 64 de ViewController.swift de la aplicación Swift Weather , no lo uso [unowned self]. Pero actualizo la interfaz de usuario usando algunos @IBOutlets como self.temperaturey self.loadingIndicator. Puede estar bien porque todos los @IBOutlets que definí 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
    }
  }
}

el enlace de la imagen está roto
Daniel Gomez Rico

@ DanielG.R. Gracias, puedo verlo. i.stack.imgur.com/Jd9Co.png
Jake Lin

2
A menos que me equivoque, el ejemplo dado en la diapositiva es incorrecto: onChangedebe ser un [weak self]cierre, ya que es una propiedad pública (internamente, pero aún así), por lo que otro objeto podría obtener y almacenar el cierre, manteniendo el objeto TempNotifier alrededor (indefinidamente si el objeto en uso no soltó el onChangecierre hasta que vio que TempNotifierse había ido, a través de su propia referencia débil a TempNotifier) . Si var onChange …fuera private var onChange …así [unowned self], sería correcto. Sin embargo, no estoy 100% seguro de esto; alguien me corrija por favor si me equivoco.
Slipp D. Thompson

@Jake Lin `var onChange: (Int) -> Void = {}` ¿las llaves representan un cierre vacío? igual que en la definición de una matriz vacía con []? No puedo encontrar la explicación en los documentos de Apple.
bibscy

@bibscy sí, {}es el cierre vacío (la instancia del cierre) como predeterminado (no hace nada), (Int) -> Voides la definición de cierre.
Jake Lin

Respuestas:


871

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

Ejemplo: hacer una solicitud de red asincrónica

Si realiza una solicitud de red asincrónica , desea que se retenga el cierre selfcuando finalice la solicitud. De lo contrario, ese objeto puede haber sido desasignado, pero aún así desea poder manejar el final de la solicitud.

Cuando usar unowned selfoweak 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 ciclo de propiedad en el que los objetos terminan siendo propietarios (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, solo necesita darse cuenta de que cualquier variable a la que se haga referencia dentro de ella, se "apropia" del cierre. Mientras el cierre esté alrededor, se garantiza que esos objetos estarán alrededor. La única forma de detener esa propiedad es hacer el [unowned self]o [weak self]. Entonces, si una clase posee un cierre, y ese cierre captura una referencia fuerte a esa clase, entonces tiene un ciclo de referencia fuerte 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, TempNotifierposee el cierre a través de la onChangevariable miembro. Si no declararan selfcomo unowned, el cierre también sería propio selfcreando un ciclo de referencia fuerte.

Diferencia entre unownedyweak

La diferencia entre unownedy weakes que weakse declara como Opcional mientras unownedque no lo es. Al declararlo, weakpuede manejar el caso de que podría ser nulo dentro del cierre en algún momento. Si intenta acceder a una unownedvariable que resulta ser nula, se bloqueará todo el programa. Por lo tanto, solo use unownedcuando sea positivo que la variable siempre estará alrededor mientras el cierre esté alrededor


1
Hola. Gran respuesta. Estoy luchando por comprenderme a mí mismo. Una razón para usar mi propio débil simplemente ser "uno mismo se convierte en una opción" no es suficiente para mí. ¿Por qué debería usar específicamente stackoverflow.com/questions/32936264/…

19
@robdashnash, la ventaja de usar self sin propietario es que no tiene que desenvolver un código opcional que puede ser innecesario si sabe con seguridad por diseño, que nunca será nulo. En última instancia, el yo sin propiedad se usa por brevedad y quizás también como una pista para futuros desarrolladores de que nunca esperas un valor nulo.
drewag

77
Un caso para usar [weak self]en una solicitud de red asíncrona es en un controlador de vista donde esa solicitud se usa para llenar la vista. Si el usuario retrocede, ya no necesitamos llenar la vista, ni necesitamos una referencia al controlador de vista.
David James

1
weaklas referencias también se establecen nilcuando el objeto se desasigna. unownedlas referencias no lo son.
BergQuester

1
Estoy un poco confundido. unownedse utiliza para non-Optionalmientras weakse utiliza para Optionallo que nuestro selfes Optionalo non-optional?
Muhammad Nayab

193

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 no propia o débil se reduce a una cuestión de vida útil de la variable y el cierre que hace referencia a ella.

rápido débil vs sin dueño

Escenarios

Puede tener dos escenarios posibles:

  1. El cierre tiene la misma vida útil de la variable, por lo que el cierre será accesible solo hasta que la variable sea accesible . La variable y el cierre tienen la misma vida útil. En este caso, debe declarar la referencia como no propiedad . 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 no se hace referencia a ellos en ningún otro lugar, no sobreviven a sus padres.

  2. La vida útil del cierre es independiente de la de la variable, aún se puede hacer referencia al cierre cuando la variable ya no es accesible. En este caso, debe declarar la referencia como débil y verificar que no sea nula antes de usarla (no fuerce 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 completamente no relacionado (de por vida).

Uso actual

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

Citando a Joe Groff de Twitter :

Sin propietario es más rápido y permite la inmutabilidad y la no opcionalidad.

Si no necesita débil, no lo use.

Encontrará más información sobre *el funcionamiento interno no propietario aquí .

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


26
Estoy cansado de escuchar la explicación del loro "usa la semana si uno mismo puede ser nulo, usa sin dueño cuando nunca puede ser nulo". Ok, lo tenemos, ¡lo escuché un millón de veces! Esta respuesta realmente profundiza en cuanto a cuándo self puede ser nulo en inglés simple, lo que responde directamente a la pregunta del OP. ¡Gracias por esta gran explicación!
TruMan1

Gracias @ TruMan1, en realidad estoy escribiendo una publicación sobre esto que terminará pronto en mi blog, actualizará la respuesta con un enlace.
Umberto Raimondi

1
Buena respuesta, muy práctica. Ahora estoy inspirado para cambiar algunos de mis vars débiles sensibles al rendimiento a los que no son de mi propiedad.
original_username

"La vida útil del cierre es independiente de la de la variable" ¿Tiene un error tipográfico aquí?
Miel el

1
Si un cierre siempre tiene la misma vida útil que el objeto principal, ¿no se resolvería el recuento de referencias de todos modos cuando se destruye el objeto? ¿Por qué no puedes simplemente usar 'self' en esta situación en lugar de molestarte con personas sin dueño o débiles?
LegendLength

105

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, sino que 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 weak, utilice las solicitudes , porque son de larga duración. El controlador de vista podría cerrarse antes de que se complete la solicitud, por lo que selfya no apunta a un objeto válido cuando se llama al cierre.
  • Si tiene un cierre que maneja un evento en un botón. Esto puede deberse a unownedque tan pronto como el controlador de vista desaparece, el botón y cualquier otro elemento al que haga referencia selfdesaparece 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.
     }

17
Este necesita más votos a favor. Dos ejemplos sólidos que muestran cómo un cierre al presionar un botón no existirá fuera de la vida útil del controlador de vista y, por lo tanto, pueden usarse sin propietario, pero la mayoría de las llamadas de red que actualizan la IU deben ser débiles.
Tim Fuqua

2
Entonces, para aclarar, ¿siempre usamos sin dueño o débil cuando nos llamamos a nosotros mismos en un bloque de cierre? ¿O hay un momento en que no llamaremos débil / sin dueño? Si es así, ¿podría dar un ejemplo para eso también?
luke

Muchas gracias.
Shawn Baek

1
Esto me dio una comprensión más profunda sobre [yo débil] y [yo sin dueño] ¡Muchas gracias @possen!
Tommy

Esto es genial. ¿Qué pasa si tengo una animación que se basa en la interacción del usuario, pero tarda un tiempo en terminar? Y luego el usuario se mueve a otra viewController. Supongo que en ese caso todavía debería estar usando en weaklugar de unowned¿verdad?
Miel el


50

Aquí hay citas brillantes de los foros de desarrolladores de Apple que describen detalles deliciosos:

unownedvs unowned(safe)vsunowned(unsafe)

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

unowned vs weak

unowneden realidad usa una implementación mucho más simple que weak. Los objetos nativos Swift llevan dos recuentos de referencias, y las unowned referencias aumentan el recuento de referencias no propiedad en lugar del recuento de referencias fuertes . 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 no propiedad también llegue a cero. Esto hace que la memoria se retenga un poco más cuando hay referencias no propiedad, pero eso no suele ser un problema cuandounowned se usa porque los objetos relacionados deberían tener vidas casi iguales de todos modos, y es mucho más simple y más bajo que la implementación basada en la tabla lateral utilizada para poner a cero las referencias débiles.

Actualización: en Swift moderno, weakinternamente utiliza el mismo mecanismo que lo unownedhace . Entonces, esta comparación es incorrecta porque compara Objective-C weakcon Swift 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 usando una referencia no propiedad después de que se desinicializa?

La memoria se mantiene viva para que sus recuentos de retención aún estén disponibles. De esta manera, cuando alguien intenta retener una referencia fuerte al objeto no propiedad, el tiempo de ejecución puede verificar que el recuento de referencia fuerte sea mayor que cero para garantizar que sea seguro retener el objeto.

¿Qué sucede con las referencias propias o no propiedad del objeto? ¿Se desacopla su vida útil del objeto cuando se desinicializa o también se retiene su memoria hasta que el objeto se desasigna después de que se libera la última referencia no propiedad?

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; aparte del encabezado con los recuentos de referencias, su contenido es basura.

Emocionado, ¿eh?


38

Hay algunas respuestas geniales aquí. Pero los cambios recientes en la forma en que Swift implementa referencias débiles deberían cambiar las decisiones débiles de uso propio de todos frente a las de uso propio no deseadas. Anteriormente, si necesitabas el mejor rendimiento, usar un self sin dueño era superior al self débil, siempre que pudieras estar seguro de que el self nunca sería nulo, porque acceder al self sin propietario es mucho más rápido que acceder al self débil.

Pero Mike Ash ha documentado cómo Swift ha actualizado la implementación de vars débiles para usar las mesas 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 uno mismo débil, creo que deberíamos usarlo de forma predeterminada en el futuro. El beneficio del self débil es que es opcional, lo que hace que sea mucho más fácil escribir un código más correcto, es básicamente la razón por la que Swift es un lenguaje tan bueno. Puede pensar que sabe qué situaciones son seguras para el uso de personas sin propiedad, pero mi experiencia al revisar muchos otros códigos de desarrolladores es, la mayoría no. He solucionado muchos bloqueos en los que se desasignó self sin propietario, generalmente en situaciones en las que un subproceso de fondo se completa después de que se desasigna un controlador.

Los errores y las fallas son las partes de programación que requieren más tiempo, son más dolorosas y costosas. Haz tu mejor esfuerzo para escribir el código correcto y evitarlos. Recomiendo que sea una regla no forzar nunca las opciones opcionales de desenvolver y nunca usar un yo sin propietario en lugar de uno débil. No perderá nada perdido las veces que el desenvolvimiento forzado y el yo sin dueño en realidad están a salvo. Pero ganará mucho eliminando errores y errores difíciles de encontrar.


Gracias por la actualización y amén en el último párrafo.
lema

1
Entonces, después de los nuevos cambios ¿Hay algún momento en el weakque no se pueda usar en lugar de un unowned?
Miel el

4

De acuerdo con Apple-doc

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

  • Si la referencia capturada nunca será nula, siempre debe capturarse como una referencia no propiedad, 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)
     }

0

Si nada de lo anterior tiene sentido:

tl; dr

Al igual que un implicitly unwrapped optional, si puede garantizar que la referencia no será nula en su punto de uso, use sin propietario. Si no, entonces deberías estar usando débil.

Explicación:

Obtuve lo siguiente a continuación en: enlace débil no propiedad . Por lo que deduje, el yo sin dueño no puede ser nulo, pero el yo débil puede ser, y el yo sin dueño puede conducir a indicadores colgantes ... algo infame en Objective-C. Espero eso ayude

"Las referencias débiles y sin propiedad se comportan de manera similar pero NO son lo mismo".

Las referencias no propias, como las referencias débiles, no aumentan el recuento de retención del objeto que se refiere. Sin embargo, en Swift, una referencia no propiedad tiene el beneficio adicional de no ser Opcional . Esto los hace más fáciles de administrar en lugar de recurrir al uso de enlaces opcionales. Esto no es diferente a los opcionales implícitamente sin envolver. Además, las referencias no propiedad no son cero . Esto significa que cuando el objeto se desasigna, no pone a cero el puntero. Esto significa que el uso de referencias no propias puede, en algunos casos, conducir a punteros colgantes. Para los nerds que recuerdan los días de Objective-C como lo hago yo, las referencias no propiedad se asignan a referencias no seguras y no retenidas.

Aquí es donde se vuelve un poco confuso.

Las referencias débiles y no propias no aumentan los conteos de retención.

Ambos pueden usarse para romper los ciclos de retención. Entonces, ¿cuándo los usamos?

Según los documentos de Apple :

“Use una referencia débil siempre que sea válida para que esa referencia se vuelva nula en algún momento durante su vida útil. Por el contrario, utilice una referencia no propiedad cuando sepa que la referencia nunca será nula una vez que se haya establecido durante la inicialización ".


0
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

Si no está seguro [unowned self] , use [weak self]

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.