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

551

Estoy desarrollando exclusivamente para iOS 5 usando ARC. En caso IBOutletes que UIViews (y subclases) sea strongo weak?

El seguimiento:

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

Se desharía de todo esto:

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

¿Hay algún problema para hacer esto? Las plantillas se utilizan al strongigual que las propiedades generadas automáticamente que se crean al conectarse directamente al encabezado desde el editor 'Interface Builder', pero ¿por qué? El UIViewControllerya tiene una strongreferencia a su viewque conserva sus subvistas.

hipercrypt
fuente
11
Como nota, IBOutletCollection()no debe ser weak, de lo contrario vuelve como nil.
ohho
Xcode 8.2.1 usa débil al crear IBOutlets a través del generador de interfaces. Sin embargo, muchas respuestas aquí en SO aconseja utilizar fuerte.
neoneye
1
@neoneye Acabo de intentar con xcode 8.3.2 arrastrar desde el guión gráfico al archivo rápido y el valor predeterminado esstrong
CupawnTae

Respuestas:

252

La mejor práctica actual recomendada por Apple es que los IBOutlets sean fuertes a menos que sean débiles específicamente necesarios para evitar un ciclo de retención. Como Johannes mencionó anteriormente, esto se comentó en la sesión "Implementación de diseños de interfaz de usuario en el generador de interfaces" 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 será retenida por la jerarquía de vistas. El único momento en el que realmente necesita debilitar una salida es si tiene una vista personalizada que hace referencia a algo que respalde la jerarquía de la vista y, en general, eso no se recomienda.

Le pregunté sobre esto en Twitter a un ingeniero del equipo del IB y él confirmó que fuerte 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

Daniel Hall
fuente
33
¿Es esto realmente cierto o la respuesta con más de 300 votos a favor es la correcta? Me di cuenta de que InterfaceBuilder por defecto usa débil cuando Ctrl-arrastra del guión gráfico al .h
Arunabh Das
44
El que tiene más de 400 votos es correcto, pero está desactualizado. Desde iOS 6 no se llama a viewDidUnload, por lo que no hay beneficios por tener salidas débiles.
kjam
77
@kjam hay beneficios. En primer lugar, no debe tener una fuerte referencia a algo que no creó. En segundo lugar, la ganancia de rendimiento es insignificante. No viole las mejores prácticas en programación simplemente porque alguien, incluso un tipo bien ubicado, dijo que esto es 10 microsegundos más rápido. Intento claro del código, no intentes jugar compilador de optimización Solo codifique el rendimiento cuando se haya medido en un caso específico como un problema.
Cameron Lowell Palmer
55
Déjame estar en desacuerdo contigo. 'Mantener una fuerte referencia a algo que no creaste' ocurre todo el tiempo en Objective-C. Es por eso que hay un recuento de referencias , en lugar de un solo propietario. ¿Tiene alguna referencia para respaldar esta recomendación? ¿Podría enumerar los otros beneficios de los puntos de venta débiles?
kjam
44
Aquí está el video WWDC mencionado en la respuesta developer.apple.com/videos/play/wwdc2015/407/?time=1946
petrsyn
450

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


Resumido de la biblioteca del desarrollador :

Desde una perspectiva práctica, en iOS y OS X las salidas deben definirse como propiedades declaradas. Los puntos de venta generalmente deben ser débiles, excepto los del Propietario del archivo a los objetos de nivel superior en un archivo plumín (o, en iOS, una escena del guión gráfico) que debe ser fuerte. Por lo tanto, los puntos de venta que cree generalmente serán débiles de forma predeterminada, 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 por 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;
Alexsander Akers
fuente
10
¿Cómo conseguiste el enlace "biblioteca de desarrollador" para saltar a la parte particular de la página de Apple Doc? Cada vez que hago un enlace a los documentos de Apple, siempre se vincula a la parte superior de la página (incluso si el contenido de interés está en la mitad de la página). Gracias.
bearMountain
68
Copié el enlace del panel de navegación a la izquierda. : D
Alexsander Akers
27
¿Qué significa "excepto para aquellos del Propietario del archivo a objetos de nivel superior en un archivo plumín (o, en iOS, una escena del guión gráfico)"?
Van Du Tran
16
@VanDuTran: significa objetos en el NIB que están en el nivel raíz, es decir, digamos que instanciaste otra vista allí que no es directamente una subvista de la vista principal, entonces debe tener una referencia fuerte.
mattjgalloway
66
El nivel superior significa que cuando miras la punta, el objeto aparece en la lista de la izquierda. Casi todas las puntas tienen una UIView en ellas; este podría ser el único objeto de nivel superior. Si agrega otros elementos y se muestran en la lista, son "objetos de nivel superior"
David H
50

Si bien la documentación recomienda usar weakpropiedades en subvistas, desde iOS 6 parece estar bien usar strong(el calificador de propiedad predeterminado) en su lugar. Eso es causado por el cambio en UIViewControllerque 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, se mantendrían en las subvistas mientras el controlador de la vista esté alrededor.
  • Desde iOS 6, las vistas ya no se descargan, sino que se cargan una vez y luego permanecen mientras su controlador esté allí. Entonces las propiedades fuertes no importan. Tampoco crearán ciclos de referencia fuertes, ya que apuntan hacia abajo en el gráfico de referencia fuerte.

Dicho esto, estoy dividido entre usar

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

y

@property (nonatomic) IBOutlet UIButton *button;

en iOS 6 y posterior:

  • El uso weakindica claramente que el controlador no desea la propiedad del botón.

  • Pero omitir weakno hace daño 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 no he encontrado una aplicación que sea demasiado lenta debido a weak IBOutlets.

  • No usar weakpuede ser percibido como un error.

En pocas palabras: desde iOS 6 ya no podemos equivocarnos mientras no usemos la descarga de vista. Tiempo de fiesta. ;)

Tammo Freese
fuente
Eso es cierto, pero es posible que desee descargar la vista usted mismo. En cuyo caso, tendría que configurar todos sus puntos de venta de forma nilmanual.
hypercrypt
PD: weakes un poco más barato en ARM64: D
hypercrypt
Así es, si implementa la descarga de vistas, las weakpropiedades o las __weakvariables de instancia son el camino a seguir. Solo quería señalar que hay menos posibilidades de error aquí. En cuanto a weakser más barato en arm64, ni siquiera he visto un problema de rendimiento en la vida real con weak IBOutlets en armv7. :)
Tammo Freese
En ese caso, strongtiene sentido también. strongsolo es perjudicial si usa la descarga de vistas, pero ¿quién lo hace en estos días? :)
Tammo Freese
2
@Rocotilos El primer iPhone tenía RAM muy limitada. Si recuerdo correctamente, 128 MB, dejando alrededor de 10 MB para la aplicación activa. Tener una pequeña huella de memoria era crucial, por lo tanto, había descarga de vistas. Eso cambió ya que ahora tenemos más y más RAM, y Apple optimizó las UIViews en iOS 6, de modo que en las advertencias de memoria, se puede liberar mucha memoria sin descargar la vista.
Tammo Freese
34

No veo ningún problema con eso. Antes del ARC, siempre he hecho mis IBOutlets assign, ya que sus supervistas ya los retienen. Si los hace weak, no debería tener que anularlos en viewDidUnload, como 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 hacerlos assign, en cuyo caso aún querría anular la referencia viewDidUnloadpara 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 geocodificación de la ubicación del usuario y establecer el código postal. Aquí está la devolución de llamada delegada:

-(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 había cerrado self.zip viewDidUnload, la devolución de llamada delegada podría generar una excepción de acceso incorrecto en self.zip.text.

Christopher Pickslay
fuente
44
También entiendo que weakno es necesario incluir las propiedades viewDidUnload. Pero, ¿por qué la plantilla de Apple para crear puntos de venta incluye un [self setMySubview:nil]?
Yang Meyer
3
¿Hay casos en el mundo real en los que el uso de fuerte / retenido para su IBOutlet podría causar problemas? ¿O es solo una retención redundante, lo que significa un mal estilo de codificación pero no afectaría su código?
Enzo Tran
1
¿Existe una retención redundante? Si hay una retención adicional, eso hará que no se cuente correctamente y, por lo tanto, no se liberará tan pronto como sea posible, ya que hay una retención adicional en su recuento de retención.
karlbecker_com
25

IBOutletdebe ser fuerte, por razones de rendimiento. Consulte Referencia del guión gráfico, IBOutlet fuerte, Escena Dock en iOS 9

Como se explica en este párrafo, las salidas a las subvistas de la vista del controlador de 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 se establece el puntero, 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 clave. Esta tabla se conoce como la tabla débil. ARC utiliza 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 a:

void objc_destroyWeak(id * object)

Luego, el objeto no está registrado y objc_destroyWeak vuelve a llamar:

objc_storeWeak(id *object, nil)

Esta contabilidad asociada con una referencia débil puede demorar 2-3 veces más que la publicación de una referencia fuerte. Por lo tanto, una referencia débil introduce una sobrecarga para el tiempo de ejecución que puede evitar simplemente definiendo salidas como fuertes.

A partir de Xcode 7, sugiere strong

Si ve la sesión 407 de WWDC 2015 Implementando diseños de UI 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 salida a una vista secundaria o a una restricción que no siempre va a ser retenida por la jerarquía de vistas.

El único momento en el que realmente necesita debilitar una salida es si tiene una vista personalizada que hace referencia a algo que respalde la jerarquía de la vista y, en general, eso no se recomienda.

Así que voy a elegir fuerte y haré clic en conectar, lo que generará mi salida.

onmyway133
fuente
1
Gran respuesta que explica la razón real
qué-
Eso es bueno y todo, pero he visto fugas provenientes de reconocedores de gestos implementados en el guión gráfico.
thibaut noah
1
No puedo entender esta línea. "El único momento en el que realmente necesita debilitar una salida es si tiene una vista personalizada que hace referencia a algo que respalde la jerarquía de la vista y, en general, eso no se recomienda". Algun ejemplo?
user1872384
Calculé el tiempo de deinit que toma débil y fuerte, y es exactamente lo mismo.
touti
Pero a la brevedad, este es más el caso. Las referencias débiles son más rápidas.
thesummersign
20

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 desactive el controlador, todas sus subvistas y salidas se liberarán automáticamente.

UiViewController utiliza la codificación de valor clave para establecer los puntos de venta utilizando referencias fuertes. Por lo tanto, cuando desasigna su UIViewController, la vista superior se desasigna automáticamente, pero también debe desasignar todas sus salidas en el método de desasignación.

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).

Giuseppe
fuente
16
Lo explica a partir de 2009. Con ARC, esto ha cambiado significativamente.
Dafydd Williams
1
:( el enlace Big Nerd Ranch está muerto ... pero realmente necesito leerlo. ¿Alguien sabe más detalles sobre esa publicación, así que puedo encontrarla?
Motti Shneor
@MottiShneor no te preocupes, no es gran cosa, ya que el enlace era una vez anterior al ARC y ya no es relevante.
Sergey Grischyov
18

Una cosa que quiero señalar aquí, y es que, 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 ú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 usa débil: https://developer.apple.com/library/ios/samplecode/Emporium/Listings/Emporium_ProductTableViewController_swift.html#//apple_ref/doc/uid/TP40016175-Emporium_ProductTableViewController_swift-DontLinkElección

Como lo hace este ejemplo de imagen en imagen: https://developer.apple.com/library/ios/samplecode/AVFoundationPiPPlayer/Listings/AVFoundationPiPPlayer_PlayerViewController_swift.html#//apple_ref/doc/uid/TP40016166-AVFlayer_Pidlive_Peller_publicidad_publicidad_publicacion_de_publicacion_de_publicacion_de_publicacion_de_publicacion

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 ubicación central: https://developer.apple.com/library/ios/samplecode/PotLoc/Listings/Potloc_PotlocViewController_swift.html#//apple_ref/doc/uid/TP40016176-Potloc_PotlocViewController_swift-DontLinkElección

Al igual que la vista ejemplo vista previa controlador: 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

Como lo hace 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_Hit_Aletal_servicio_Control_Actualizador_de_Archivo_De_At_sitio_de_Archivo_de_Archivo_de_Archivo_de_Archivo_de_Archivo_de_Archivado

Todos están completamente actualizados para iOS 9, y todos usan salidas débiles. De esto aprendemos que A. El problema no es tan simple como algunas personas piensan que es. 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 el tema un poco mejor!

Cuídate.

syedfa
fuente
He estado revisando este problema por algún tiempo y no he encontrado ninguna respuesta concreta. Dado que el enlace anterior sugiere que ambos están bien y en general van con lo que Xcode autosuggests.
subin272
6

Tenga en cuenta, IBOutletCollectiondebería ser @property (strong, nonatomic).

Landonandrey
fuente
3
¿Por qué no copycomo es un NSArray?
hipercrypt
5

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 interfaz de usuario en Interface Builder y comienza a las 32:30. Mi nota de lo que dice es (casi, si no exactamente, citandolo):

  • 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 algo que se respalde en la jerarquía de vistas y, en general, no se recomienda

En otros barrios, siempre debería ser fuerte siempre que parte de nuestra vista personalizada no cree un ciclo de retención con parte de la vista 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 mantienen la referencia? ¿O por qué sucedió ese cambio? Creo que la respuesta es anterior en esta charla cuando describen cómo se crean las puntas a partir de la 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.

Julian Król
fuente
4

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 necesitas hacer nada en viewDidUnload

kraag22
fuente