iphone - ¿Qué pasa si no retengo IBOutlet?
retain (6)
Si hago esto:
@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
en lugar de esto:
@interface RegisterController : UIViewController <UITextFieldDelegate>
{
UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;
¿Ocurrirá algo malo? Sé que en el segundo caso, el campo se conserva, pero ¿esto hace una diferencia ya que la punta es propietaria del campo? ¿Se irá el campo sin retener? y bajo que circunstancias? El código en el primer caso funciona, se preguntaba si esto es un problema o no en términos de gestión de memoria.
Bueno, en el segundo caso, está agregando un método get / set para ese IBOutlet en particular. Cada vez que agrega un método de obtención / configuración, (casi siempre) desea que se configure para problemas de administración de memoria. Creo que una mejor manera de plantear tu pregunta hubiera sido esta:
@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
@property (nonatomic) IBOutlet UITextField *usernameField;
o
@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;
En ese caso, entonces sí, deberá agregar una retención ya que afectará la administración de la memoria. A pesar de que puede no tener ningún efecto, si está agregando y eliminando programáticamente IBOutlet, podría tener problemas.
Como regla general: siempre agregue una propiedad @ (con retención) siempre que tenga un IBOutlet.
De hecho hay dos modelos:
El viejo modelo
Este modelo fue el modelo anterior a Objective-C 2.0 y se heredó de Mac OS X. Todavía funciona, pero no debe declarar propiedades para modificar los ivars. Es decir:
@interface StrokeWidthController : UIViewController {
IBOutlet UISlider* slider;
IBOutlet UILabel* label;
IBOutlet StrokeDemoView* strokeDemoView;
CGFloat strokeWidth;
}
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end
En este modelo, no conserva los ivars de IBOutlet, pero debe liberarlos. Es decir:
- (void)dealloc {
[slider release];
[label release];
[strokeDemoView release];
[super dealloc];
}
El nuevo modelo
Tienes que declarar propiedades para las variables de IBOutlet:
@interface StrokeWidthController : UIViewController {
IBOutlet UISlider* slider;
IBOutlet UILabel* label;
IBOutlet StrokeDemoView* strokeDemoView;
CGFloat strokeWidth;
}
@property (retain, nonatomic) UISlider* slider;
@property (retain, nonatomic) UILabel* label;
@property (retain, nonatomic) StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end
Además tienes que liberar las variables en dealloc:
- (void)dealloc {
self.slider = nil;
self.label = nil;
self.strokeDemoView = nil;
[super dealloc];
}
Además, en plataformas no frágiles puede eliminar los ivars:
@interface StrokeWidthController : UIViewController {
CGFloat strokeWidth;
}
@property (retain, nonatomic) IBOutlet UISlider* slider;
@property (retain, nonatomic) IBOutlet UILabel* label;
@property (retain, nonatomic) IBOutlet StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end
LA COSA MIRADA
En ambos casos, las salidas se configuran llamando a setValue: forKey :. El tiempo de ejecución interno (en particular _decodeObjectBinary) comprueba si existe el método de establecimiento. Si no existe (solo existe el ivar), envía una retención adicional al ivar. Por este motivo, no debe conservar el IBOutlet si no hay un método de establecimiento.
Declarar algo IBOutlet, desde un punto de vista de gestión de memoria, no hace nada (IBOutlet está literalmente # definido como nada). La única razón para incluir IBOutlet en la declaración es si tiene la intención de conectarlo en Interface Builder (para eso es la declaración de IBOutlet, una sugerencia para IB).
Ahora, la única razón para hacer una propiedad @ para una variable de instancia es si tiene la intención de asignarlos programáticamente. Si no lo hace (es decir, solo está configurando su UI en IB), no importa si hace una propiedad o no. No hay razón para, IMO.
Vuelve a tu pregunta. Si solo está configurando este ivar (usernameField) en IB, no se moleste con la propiedad, no afectará nada. Si usted HACE una propiedad para usernameField (porque la está creando programáticamente), definitivamente cree una propiedad para ella, y DEBE hacer que la propiedad se retenga si es así.
Los objetos en el archivo de plumilla se crean con una cuenta de retención de 1 y luego se liberan automáticamente. A medida que reconstruye la jerarquía de objetos, UIKit restablece las conexiones entre los objetos usando setValue: forKey :, que usa el método de establecimiento disponible o retiene el objeto de forma predeterminada si no hay disponible ningún método de establecimiento. Esto significa que cualquier objeto para el que tenga una salida sigue siendo válido. Sin embargo, si hay objetos de nivel superior que no almacena en puntos de venta, debe conservar la matriz devuelta por el método loadNibNamed: owner: options: o los objetos dentro de la matriz para evitar que esos objetos se liberen prematuramente.
No hay ninguna diferencia entre la forma en que funcionan estas dos definiciones de interfaz hasta que empiece a usar los elementos de acceso proporcionados por la propiedad.
En ambos casos, aún deberá liberar y configurar a nulo el IBOutlet en sus métodos dealloc o viewDidUnload.
El IBOutlet apunta a un objeto instanciado dentro de un archivo XIB. Ese objeto es propiedad del objeto Propietario del archivo XIB (generalmente el controlador de vista en el que se declara el IBOutlet).
Debido a que el objeto se crea como resultado de cargar el XIB, el recuento de retención es 1 y es propiedad del propietario de su archivo, como se mencionó anteriormente. Esto significa que el Propietario del archivo es responsable de liberarlo cuando se desasigna.
La adición de la declaración de propiedad con el atributo de retención simplemente especifica que el método de establecimiento debe conservar el objeto que se pasa para que se establezca, que es la forma correcta de hacerlo. Si no especificó retener en la declaración de propiedad, el IBOutlet posiblemente podría apuntar a un objeto que ya no existe, debido a que fue publicado por su propietario o lanzado automáticamente en algún momento del ciclo de vida del programa. Retenerlo evita que el objeto se desasigne hasta que haya terminado con él.
Se recomienda que declare propiedades para todos sus IBOutlets para mayor claridad y consistencia. Los detalles se detallan en la Guía de programación de gestión de memoria . La idea básica es que, cuando los objetos NIB no están archivados, el código de carga de la plumilla pasará y establecerá todos los IBOutlets utilizando setValue: forKey :. Cuando declara el comportamiento de administración de memoria en la propiedad, no hay ningún misterio en cuanto a lo que está sucediendo. Si la vista se descarga, pero usó una propiedad que fue declarada como retenida, todavía tiene una referencia válida a su campo de texto.
Quizás un ejemplo más concreto sería útil para indicar por qué debería usar una propiedad de retención:
Voy a hacer algunas suposiciones sobre el contexto en el que está trabajando. Asumiré que el siguiente campo de UITextField es una subvista de otra vista que está controlada por un UIViewController. Asumiré que en algún momento, la vista está fuera de la pantalla (tal vez se usa en el contexto de un controlador de control UIN) y que en algún momento su aplicación recibe una advertencia de memoria.
Entonces digamos que su subclase UIViewController necesita acceder a su vista para mostrarla en la pantalla. En este punto, se cargará el archivo de la plumilla y cada una de las propiedades de IBOutlet se establecerá mediante el código de carga de la plumilla usando setValue: forKey :. Los más importantes a tener en cuenta aquí son la vista de nivel superior que se establecerá en la propiedad de vista UIViewController (que conservará esta vista de nivel superior) y su UITextField, que también se mantendrá. Si está simplemente configurado, el código de carga de la plumilla lo retendrá, de lo contrario, la propiedad lo habrá conservado. El UITextField también será una subvista de la vista UIV de nivel superior, por lo que tendrá una retención adicional, al estar en la matriz de subvistas de la vista de nivel superior, por lo que en este punto el campo de texto se ha retenido dos veces.
En este punto, si desea cambiar el campo de texto mediante programación, puede hacerlo. El uso de la propiedad hace que la administración de la memoria sea más clara aquí; acaba de establecer la propiedad con un nuevo campo de texto lanzado automáticamente. Si no ha utilizado la propiedad, debe recordar liberarla y, opcionalmente, conservar la nueva. En este punto, es un tanto ambiguo en cuanto a quién posee este nuevo campo de texto, porque la semántica de administración de memoria no está contenida dentro del configurador.
Ahora digamos que un controlador de vista diferente se empuja en la pila del controlador de navegación UIN, de modo que esta vista ya no está en primer plano. En el caso de una advertencia de memoria, la vista de este controlador de vista fuera de pantalla se descargará. En este punto, la propiedad de vista del nivel superior UIView se anulará, se liberará y se desasignará.
Debido a que el UITextField se estableció como una propiedad que se retuvo, el UITextField no se desasigna, ya que habría sido su única retención la de la matriz de subvistas de la vista de nivel superior.
Si, por el contrario, la variable de instancia para el UITextField no se configuró a través de una propiedad, también estaría presente, ya que el código de carga de la punta se había conservado al configurar la variable de instancia.
Un punto interesante que se destaca es que debido a que el UITextField también se retiene a través de la propiedad, es probable que no quiera mantenerlo cerca en caso de una advertencia de memoria. Por este motivo, debe anular la propiedad en el método - [UIViewController viewDidUnload]. Esto eliminará la versión final en el UITextField y la desasignará según lo previsto. Si usa la propiedad, debe recordar liberarla explícitamente. Si bien estas dos acciones son funcionalmente equivalentes, la intención es diferente.
Si en lugar de cambiar el campo de texto, eligió eliminarlo de la vista, es posible que ya lo haya eliminado de la jerarquía de vistas y que haya establecido la propiedad en nulo, o que haya liberado el campo de texto. Si bien es posible escribir un programa correcto en este caso, es fácil cometer el error de liberar el campo de texto en el método viewDidUnload. Sobresalir un objeto es un error que provoca un fallo; establecer una propiedad que ya es nil de nuevo en nil no lo es.
Es posible que mi descripción haya sido demasiado detallada, pero no quise omitir ningún detalle en el escenario. El simple hecho de seguir las pautas lo ayudará a evitar problemas a medida que se encuentre con situaciones más complejas.
También vale la pena señalar que el comportamiento de la administración de memoria difiere en Mac OS X en el escritorio. En el escritorio, configurar un IBOutlet sin un configurador no conserva la variable de instancia; pero nuevamente usa el setter si está disponible.