¿IBOutlets debería ser fuerte o débil bajo ARC?

Estoy desarrollando exclusivamente para iOS 5 usando ARC. ¿Deberían los IBOutlet s para UIView s (y subclases) ser strong o weak ?

El seguimiento:

 @property (nonatomic, weak) IBOutlet UIButton *button; 

Me desharía de todo esto:

 - (void)viewDidUnload { // ... self.button = nil; // ... } 

¿Hay algún problema al hacer esto? Las plantillas están usando strong igual que las propiedades generadas automáticamente cuando se conectan directamente al encabezado desde el editor de ‘Interface Builder’, pero ¿por qué? El UIViewController ya tiene una strong referencia a su view que conserva sus subvistas.

La mejor práctica recomendada actual de Apple es que IBOutlets sea fuerte a menos que se necesite específicamente la debilidad para evitar un ciclo de retención. Como mencionara anteriormente Johannes, esto se comentó en la sesión “Implementación de diseños de interfaz de usuario en Interface Builder” de WWDC 2015, donde un ingeniero de Apple dijo:

Y la última opción que quiero señalar es el tipo de almacenamiento, que puede ser fuerte o débil. En general, debe fortalecer su salida, especialmente si está conectando una toma de stream a una subvista o una restricción que la jerarquía de vista no siempre retendrá. La única vez que realmente necesita debilitar una salida es si tiene una vista personalizada que haga referencia a algo que respalde la jerarquía de vistas y, en general, eso no es recomendable.

Le pregunté sobre esto en Twitter a un ingeniero del equipo del IB y me confirmó que debería ser el valor predeterminado y que los documentos del desarrollador se están actualizando.

https://twitter.com/_danielhall/status/620716996326350848 https://twitter.com/_danielhall/status/620717252216623104

ADVERTENCIA, RESPUESTA DESCONTINUADA : esta respuesta no está actualizada según WWDC 2015, para la respuesta correcta, consulte la respuesta aceptada (Daniel Hall) más arriba. Esta respuesta quedará para registro.


Resumido de la biblioteca del desarrollador :

Desde una perspectiva práctica, en iOS y OS X las salidas deben definirse como propiedades declaradas. Por lo general, los puntos de venta deberían ser débiles, excepto aquellos desde el propietario del archivo hasta los objetos de nivel superior en un archivo de punta (o, en iOS, una escena del guión gráfico) que deberían ser fuertes. Por lo tanto, los puntos de venta que usted cree típicamente serán débiles por defecto, porque:

  • Los outlets que crea, por ejemplo, las subvistas de la vista de un controlador de vista o la ventana de un controlador de ventana, son referencias arbitrarias entre objetos que no implican propiedad.

  • Los puntos fuertes son frecuentemente especificados por las clases de framework (por ejemplo, UIViewController’s view outlet, o NSWindowController’s window outlet).

     @property (weak) IBOutlet MyView *viewContainerSubview; @property (strong) IBOutlet MyOtherClass *topLevelObject; 

Si bien la documentación recomienda el uso de propiedades weak en las subvistas, desde iOS 6 parece estar bien usar strong (el calificador de propiedad predeterminado) en su lugar. Esto se debe al cambio en UIViewController que las vistas ya no se descargan.

  • Antes de iOS 6, si mantenía vínculos fuertes con las subvistas de la vista del controlador, si la vista principal del controlador de vista se descargaba, esas se mantendrían en las subvistas siempre que el controlador de vista esté cerca.
  • Desde iOS 6, las vistas ya no se descargan, sino que se cargan una vez y luego se mantienen mientras el controlador esté allí. Las propiedades tan fuertes no importarán. Tampoco crearán fuertes ciclos de referencia, ya que apuntan hacia abajo el fuerte gráfico de referencia.

Dicho esto, estoy dividido entre el uso

 @property (nonatomic, weak) IBOutlet UIButton *button; 

y

 @property (nonatomic) IBOutlet UIButton *button; 

en iOS 6 y después:

  • El uso de weak indica claramente que el controlador no quiere la propiedad del botón.

  • Pero omitir weak no duele en iOS 6 sin descarga de vista, y es más corto. Algunos pueden señalar que también es más rápido, pero todavía tengo que encontrar una aplicación que es demasiado lenta debido a IBOutlet s weak .

  • No usar weak puede ser percibido como un error.

En pocas palabras: desde iOS 6 no podemos seguir haciendo esto mal siempre que no usemos la vista de descarga. Tiempo de fiesta. 😉

No veo ningún problema con eso. Pre-ARC, siempre he asignado mis IBOutlets, ya que sus supervistas ya los conservan. Si los weak , no debería tener que eliminarlos en viewDidUnload, como usted señala.

Una advertencia: puede admitir iOS 4.x en un proyecto ARC, pero si lo hace, no puede usar weak , por lo que tendría que assign , en cuyo caso aún desea borrar la referencia en viewDidUnload para evitar un puntero colgante. Aquí hay un ejemplo de un error de puntero colgante que he experimentado:

Un UIViewController tiene un UITextField para el código postal. Utiliza CLLocationManager para revertir la geoencoding de la ubicación del usuario y establecer el código postal. Aquí está la callback del delegado:

 -(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { Class geocoderClass = NSClassFromString(@"CLGeocoder"); if (geocoderClass && IsEmpty(self.zip.text)) { id geocoder = [[geocoderClass alloc] init]; [geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) { if (self.zip && IsEmpty(self.zip.text)) { self.zip.text = [[placemarks objectAtIndex:0] postalCode]; } }]; } [self.locationManager stopUpdatingLocation]; } 

Descubrí que si descartaba esta vista en el momento correcto y no me encontraba en viewDidUnload en viewDidUnload , la callback del delegado podía generar una excepción de acceso incorrecto en self.zip.text.

En el desarrollo de iOS, la carga de NIB es un poco diferente del desarrollo de Mac.

En el desarrollo de Mac, un IBOutlet suele ser una referencia débil: si tiene una subclase de NSViewController, solo se conservará la vista de nivel superior y cuando desasigne el controlador, todas sus subvistas y salidas se liberarán automáticamente.

UiViewController usa Key Value Coding para establecer los puntos de venta usando referencias fuertes. Entonces, cuando desasigne su UIViewController, la vista superior se desasignará automáticamente, pero también debe desasignar todas sus salidas en el método dealloc.

En esta publicación de Big Nerd Ranch , cubren este tema y también explican por qué usar una referencia fuerte en IBOutlet no es una buena opción (incluso si Apple lo recomienda en este caso).

IBOutlet debe ser fuerte, por razones de rendimiento. Ver Storyboard Reference, Strong IBOutlet, Scene Dock en iOS 9

Como se explica en este párrafo, las salidas a las subvistas de la vista del controlador de la vista pueden ser débiles, porque estas subvistas ya son propiedad del objeto de nivel superior del archivo nib. Sin embargo, cuando un Outlet se define como un puntero débil y el puntero se establece, ARC llama a la función de tiempo de ejecución:

id objc_storeWeak(id *object, id value);

Esto agrega el puntero (objeto) a una tabla usando el valor del objeto como una clave. Esta tabla se conoce como la tabla débil. ARC usa esta tabla para almacenar todos los punteros débiles de su aplicación. Ahora, cuando el valor del objeto se desasigna, ARC iterará sobre la tabla débil y establecerá la referencia débil en nil. Alternativamente, ARC puede llamar:

void objc_destroyWeak(id * object)

Entonces, el objeto no está registrado y objc_destroyWeak llama nuevamente:

objc_storeWeak(id *object, nil)

Esta contabilidad asociada a una referencia débil puede demorar entre 2 y 3 veces más que una referencia fuerte. Por lo tanto, una referencia débil introduce una sobrecarga para el tiempo de ejecución que puede evitar simplemente definiendo puntos de venta como fuertes.

A partir de Xcode 7, sugiere strong

Si ve WWDC 2015 sesión 407 Implementación de diseños de interfaz de usuario en Interface Builder , sugiere (transcripción de http://asciiwwdc.com/2015/sessions/407 )

Y la última opción que quiero señalar es el tipo de almacenamiento, que puede ser fuerte o débil.

En general, debe fortalecer su salida, especialmente si está conectando una toma de stream a una subvista o a una restricción que la jerarquía de vista no siempre retendrá.

La única vez que realmente necesita debilitar una salida es si tiene una vista personalizada que haga referencia a algo que respalde la jerarquía de vistas y, en general, eso no es recomendable.

Así que voy a elegir fuerte y voy a hacer clic en conectar que generará mi salida.

Una cosa que deseo señalar aquí, y eso es, a pesar de lo que los ingenieros de Apple han declarado en su propio video WWDC 2015 aquí:

https://developer.apple.com/videos/play/wwdc2015/407/

Apple sigue cambiando de opinión sobre el tema, lo que nos dice que no hay una sola respuesta correcta para esta pregunta. Para demostrar que incluso los ingenieros de Apple están divididos en este tema, eche un vistazo al código de muestra más reciente de Apple, y verá que algunas personas usan débiles, y otras no.

Este ejemplo de Apple Pay usa weak: https://developer.apple.com/library/ios/samplecode/Emporium/Listings/Emporium_ProductTableViewController_swift.html#//apple_ref/doc/uid/TP40016175-Emporium_ProductTableViewController_swift-DontLinkElementID_8

Al igual que este ejemplo de imagen en imagen: https://developer.apple.com/library/ios/samplecode/AVFoundationPiPPlayer/Listings/AVFoundationPiPPlayer_PlayerViewController_swift.html#//apple_ref/doc/uid/TP40016166-AVFoundationPiPPlayer_PlayerViewController_swift-DontLinkElementID_4

Al igual que el ejemplo de Lister: https://developer.apple.com/library/ios/samplecode/Lister/Listings/Lister_ListCell_swift.html#//apple_ref/doc/uid/TP40014701-Lister_ListCell_swift-DontLinkElementID_57

Al igual que el ejemplo de Core Location: https://developer.apple.com/library/ios/samplecode/PotLoc/Listings/Potloc_PotlocViewController_swift.html#//apple_ref/doc/uid/TP40016176-Potloc_PotlocViewController_swift-DontLinkElementID_6

Al igual que el ejemplo de previsualización del controlador de vista: https://developer.apple.com/library/ios/samplecode/ViewControllerPreviews/Listings/Projects_PreviewUsingDelegate_PreviewUsingDelegate_DetailViewController_swift.html#//apple_ref/doc/uid/TP40016546-Projects_PreviewUsingDelegate_PreviewUsingDelegate_DetailViewController_swift-DontLinkElementID_5

Al igual que el ejemplo de HomeKit: https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Listings/HMCatalog_Homes_Action_Sets_ActionSetViewController_swift.html#//apple_ref/doc/uid/TP40015048-HMCatalog_Homes_Action_Sets_ActionSetViewController_swift-DontLinkElementID_23

Todos estos están completamente actualizados para iOS 9, y todos usan puntos débiles. De esto aprendemos que A. El problema no es tan simple como algunos creen. B. Apple ha cambiado de opinión varias veces, y C. Puedes usar lo que sea que te haga feliz 🙂

Un agradecimiento especial a Paul Hudson (autor de http://www.hackingwithsift.com) que me dio la aclaración y referencias para esta respuesta.

¡Espero que esto aclare el tema un poco mejor!

Cuídate.

Desde la WWDC 2015 hay una sesión sobre Implementación de diseños de interfaz de usuario en Interface Builder . Alrededor de la marca de los @IBOutlet , dice que siempre quiere hacer que su @IBOutlet fuerte .

Tenga en cuenta que IBOutletCollection debe ser @property (strong, nonatomic) .

Parece que algo ha cambiado a lo largo de los años y ahora Apple recomienda usar productos fuertes en general. La evidencia en su sesión WWDC está en la sesión 407 – Implementación de diseños de interfaz de usuario en Interface Builder y comienza a las 32:30. Mi nota de lo que dice es (casi, si no exactamente, citarlo):

  • las conexiones de salida en general deberían ser fuertes, especialmente si conectamos una subvista o restricción que no siempre es retenida por la jerarquía de vistas

  • Es posible que se necesite una conexión de salida débil al crear vistas personalizadas que tengan alguna referencia a alguna copia de seguridad en la jerarquía de vistas y, en general, no se recomienda

En otras salas debería ser siempre fuerte siempre que algunas de nuestras vistas personalizadas no creen un ciclo de retención con algo de la vista hacia arriba en la jerarquía de vistas

EDITAR:

Algunos pueden hacer la pregunta. ¿Mantenerlo con una referencia fuerte no crea un ciclo de retención ya que el controlador de vista raíz y la vista propietaria conservan la referencia al mismo? ¿O por qué sucedió eso? Creo que la respuesta es anterior en esta charla cuando describen cómo se crean las puntas desde el xib. Hay una punta separada creada para un VC y para la vista. Creo que esta podría ser la razón por la que cambian las recomendaciones. Aún así sería bueno obtener una explicación más profunda de Apple.

Creo que la información más importante es: los elementos en xib están automáticamente en subvistas de vista. Subviews es NSArray. NSArray posee sus elementos. etc tienen fuertes consejos sobre ellos. Por lo tanto, en la mayoría de los casos, no desea crear otro puntero fuerte (IBOutlet)

Y con ARC no necesita hacer nada en viewDidUnload