ios - ¿Deben los IBOutlets ser fuertes o débiles bajo ARC?




objective-c cocoa-touch interface-builder automatic-ref-counting (10)

Estoy desarrollando exclusivamente para iOS 5 usando ARC. ¿Deben ser IBOutlet s a UIView s (y subclases) strong o weak ?

El seguimiento:

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

Se libraría de todo esto:

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

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


Answers

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

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

Dicho esto, estoy desgarrado entre usar

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

y

@property (nonatomic) IBOutlet UIButton *button;

en iOS 6 y después:

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

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

  • No usar weak puede ser percibido como un error.

Conclusión: desde iOS 6 no podemos equivocarnos más mientras no usemos la descarga de vistas. Tiempo de fiesta. ;)


No veo ningún problema con eso. Pre-ARC, siempre he asignado mis IBOutlets, ya que ya están retenidos por sus superviews. Si los weak , no deberías tener que eliminarlos en viewDidUnload, como lo indicas.

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 querría anular la referencia en viewDidUnload para evitar un puntero que cuelga. 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 invertir geocodificando la ubicación del usuario y establecer el código postal. Aquí está la devolución de llamada 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 adecuado y no anulaba self.zip en viewDidUnload , la devolución de llamada de delegado podría generar una excepción de acceso incorrecto en self.zip.text.


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

  • Las conexiones de salida en general deben ser sólidas, 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 toma de corriente débil al crear vistas personalizadas que tengan alguna referencia a una copia de seguridad en la jerarquía de vistas y, en general, no se recomienda

En otras áreas, debería ser siempre fuerte ahora, siempre que alguna de nuestras vistas personalizadas no cree un ciclo de retención con algunas de las vistas arriba en la jerarquía de vistas.

EDITAR:

Algunos pueden hacer la pregunta. ¿Mantenerlo con una referencia sólida no crea un ciclo de retención como el controlador de vista raíz y la vista propietaria mantiene la referencia a él? ¿O por qué cambió eso? Creo que la respuesta es anterior en esta charla cuando describen cómo se crean las puntas del 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.


La mejor práctica recomendada actual de Apple es que los IBOutlets sean fuertes, a menos que se necesite específicamente una forma débil para evitar un ciclo de retención. Como Johannes mencionó anteriormente, esto se comentó en la sesión "Implementación de diseños de UI 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 salida a una subvista o a una restricción que no siempre va a ser retenida por la jerarquía de vistas. La única vez que realmente necesita debilitar una salida es si tiene una vista personalizada que haga referencia a una copia de seguridad de la jerarquía de vistas y, en general, no se recomienda.

Pregunté sobre esto en Twitter a un ingeniero del equipo de IB y él confirmó que fuerte debe ser el predeterminado y que se están actualizando los documentos del desarrollador.

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


ADVERTENCIA, RESPUESTA ACTUALIZADA : esta respuesta no está actualizada según WWDC 2015, para la respuesta correcta, refiérase a la respuesta aceptada (Daniel Hall) más arriba. Esta respuesta quedará para el registro.

Resumido de la biblioteca de desarrolladores :

Desde una perspectiva práctica, en iOS y OS X, las salidas deben definirse como propiedades declaradas. En general, los puntos de venta deben ser débiles, excepto los del Propietario del archivo a objetos de nivel superior en un archivo de plumilla (o, en iOS, una escena del guión gráfico) que debe ser fuerte. Por lo tanto, las salidas que cree normalmente serán débiles por defecto, porque:

  • Las salidas que crea, por ejemplo, en subvistas de la vista de un controlador de vista o de la ventana de un controlador de ventana, son referencias arbitrarias entre objetos que no implican propiedad.

  • Las salidas fuertes se especifican con frecuencia mediante clases de marco (por ejemplo, salida de vista de UIViewController o salida de ventana de NSWindowController).

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

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 punteros fuertes en ellos. Entonces, en la mayoría de los casos, no desea crear otro puntero fuerte (IBOutlet)

Y con ARC no necesita hacer nada en viewDidUnload


Una cosa que quiero señalar aquí, y eso es, a pesar de lo que los ingenieros de Apple han declarado en su propio video de la 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 única respuesta correcta a 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ébil y otras no.

Este ejemplo de Apple Pay utiliza debilidad: 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 en 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 la Ubicación del Núcleo: https://developer.apple.com/library/ios/samplecode/PotLoc/Listings/Potloc_PotlocViewController_swift.html#//apple_ref/doc/uid/TP40016176-Potloc_PotlocViewController_swift-DontLinkElementID_6

Como lo hace el ejemplo de vista previa del controlador de la 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 en 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 ellos están completamente actualizados para iOS 9, y todos utilizan puntos de venta débiles. De esto aprendemos que A. El problema no es tan simple como algunas personas lo hacen ser. B. Apple ha cambiado de opinión repetidamente, y C. Puedes usar lo que te haga feliz :)

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

Espero que esto aclare un poco mejor el tema!

Cuídate.


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


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 al controlador todas sus subvistas y salidas se liberan automáticamente.

UiViewController usa la codificación de valor clave para establecer los puntos de venta con referencias sólidas. Por lo tanto, cuando desasigne su UIViewController, la vista superior se desasignará automáticamente, pero también deberá desasignar todos sus puntos de venta 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)


También puedes hacerlo fácilmente en código. En mi caso, tengo un UIStackView que es la única subvista de un UIScrollView

// Create the stack view
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
// Add things to the stack view....

// Add it as a subview to the scroll view
scrollView.addSubview(stackView)

// Use auto layout to pin the stack view's sides to the scroll view
NSLayoutConstraint.activate([
    stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
    scrollView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),

    stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
    scrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
])

// Now make sure the thing doesn't scroll horizontally
let margin: CGFloat = 40

scrollView.contentInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)

scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true

let stackViewWidthConstraint = stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
stackViewWidthConstraint.constant = -(margin * 2)
stackViewWidthConstraint.isActive = true

El bit NSLayoutConstraint.activate se toma de la excelente extensión UIView de Dave DeLong aquí: https://github.com/davedelong/MVCTodo/blob/master/MVCTodo/Extensions/UIView.swift#L26





ios objective-c cocoa-touch interface-builder automatic-ref-counting