¿Qué sucede si no retiene IBOutlet?

Si hago esto:

@interface RegisterController : UIViewController  { IBOutlet UITextField *usernameField; } 

en lugar de esto:

 @interface RegisterController : UIViewController  { UITextField *usernameField; } @property (nonatomic, retain) IBOutlet UITextField *usernameField; 

¿Pasará algo malo? Sé que en el segundo caso, el campo se retiene, pero ¿esto hace que sea diferente ya que la punta posee el campo? ¿Se irá el campo sin retener? y bajo qué 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.

Se recomienda que declare las propiedades de todos sus IBOutlet para mayor claridad y consistencia. Los detalles están detallados en la Guía de progtwigción de administración de memoria . La esencia básica es que, cuando los objetos de NIB se desarchivan, el código de carga de la punta pasará y establecerá todas las IBOutlet utilizando setValue: forKey :. Cuando declara el comportamiento de administración de memoria en la propiedad, no hay misterio en cuanto a lo que está sucediendo. Si la vista se descarga, pero usó una propiedad que se declaró como retención, todavía tiene una referencia válida para su campo de texto.

Quizás un ejemplo más concreto sería útil para indicar por qué debería usar una propiedad retenida:

Voy a hacer algunas suposiciones sobre el contexto en el que estás trabajando: supongo que el UITextField anterior es una subvista de otra vista que está controlada por un UIViewController. Asumiré que en algún punto, la vista está fuera de la pantalla (tal vez se usa en el contexto de un UINavigationController), 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, el archivo de punta se cargará y cada propiedad de IBOutlet será establecida por el código de carga de punta utilizando 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 de UIViewController (que conservará esta vista de nivel superior) y su UITextField, que también se conservará. Si simplemente se configura, el código de carga del plumín lo tendrá retenido, de lo contrario la propiedad lo retendrá. El UITextField también será una subvista del UIView de nivel superior, por lo que tendrá un retener adicional en el conjunto 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 progtwigción, puede hacerlo. Usar la propiedad hace que la administración de la memoria sea más clara aquí; usted acaba de establecer la propiedad con un nuevo campo de texto de autorrellenido. Si no ha utilizado la propiedad, debe recordar liberarla y, opcionalmente, conservar la nueva. En este punto, es algo ambiguo en cuanto a quién posee este nuevo campo de texto, porque la semántica de gestión de memoria no está contenida dentro del setter.

Ahora digamos que un controlador de vista diferente se inserta en la stack del Controlador de NAVEGACIÓN, para 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 UIView de nivel superior será anulada, será liberada y desasignada.

Debido a que UITextField se estableció como una propiedad que se retuvo, el UITextField no se desasigna, como lo hubiera sido si hubiera sido el único de la matriz de subvistas de la vista de nivel superior.

Si, en cambio, la variable de instancia para UITextField no se hubiera establecido a través de una propiedad, también estaría disponible, ya que el código de carga de la punta lo había conservado al establecer la variable de instancia.

Un punto interesante que resalta es que debido a que el UITextField se retiene adicionalmente a través de la propiedad, es probable que no desee mantenerlo en caso de una advertencia de memoria. Por esta razón, debe anular la propiedad en el método – [UIViewController viewDidUnload]. Esto eliminará la versión final en el UITextField y lo 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, elige eliminarlo de la vista, es posible que ya lo haya eliminado de la jerarquía de vistas y haya establecido la propiedad en cero, o haya liberado el campo de texto. Si bien es posible escribir un progtwig correcto en este caso, es fácil cometer el error de sobreexponer el campo de texto en el método viewDidUnload. El exceso de liberación de un objeto es un error de inducción de choque; establecer una propiedad que ya es nula de nuevo a cero no lo es.

Mi descripción puede haber sido demasiado prolija, pero no quería omitir ningún detalle en el escenario. Simplemente siguiendo las pautas ayudará a evitar problemas a medida que encuentre situaciones más complejas.

También vale la pena señalar que el comportamiento de administración de memoria difiere en Mac OS X en el escritorio. En el escritorio, la configuración de una IBOutlet sin setter no conserva la variable de instancia; pero de nuevo usa el colocador si está disponible.

Declarar algo IBOutlet, desde el 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 statement es si tiene la intención de conectarlo en Interface Builder (para eso está la statement 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.

De vuelta a tu pregunta. Si solo está configurando este ivar (campo de usuario) en IB, no se moleste con la propiedad, no afectará nada. Si HACE una propiedad para el nombre de usuario Campo (porque está programáticamente creando), definitivamente, haga una propiedad para él, y SIEMPRE haga que la propiedad conserve si es así.

De hecho, hay dos modelos:

EL MODELO ANTIGUO

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 retiene IBivados de IBOutlet, pero debe liberarlos. Es decir:

 - (void)dealloc { [slider release]; [label release]; [strokeDemoView release]; [super dealloc]; } 

EL NUEVO MODELO

Debe declarar propiedades para las variables 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, debe liberar las variables en dealloc:

 - (void)dealloc { self.slider = nil; self.label = nil; self.strokeDemoView = nil; [super dealloc]; } 

Furthermode, en plataformas no frágiles puedes 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 

LO EXTRAÑO

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 setter. Si no existe (solo existe el ivar), envía un retener adicional al ivar. Por esta razón, no debe retener el IBOutlet si no hay un método setter.

No hay ninguna diferencia entre la forma en que esas dos definiciones de interfaz funcionan hasta que comienza a utilizar los accesos proporcionados por la propiedad.

En ambos casos, aún necesitará liberar y configurar para anular la IBOutlet en sus métodos dealloc o viewDidUnload.

IBOutlet apunta a un objeto instanciado dentro de un archivo XIB. Ese objeto es propiedad del objeto Dueño del archivo 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, su conteo de retención es 1 y es propiedad del propietario del archivo, como se mencionó anteriormente. Esto significa que el propietario del archivo es responsable de liberarlo cuando se desasigna.

Al agregar la statement de propiedad con el atributo de retención, simplemente se especifica que el método setter debe retener el objeto pasado para establecerse, que es la forma correcta de hacerlo. Si no especificó “retener” en la statement de propiedad, IBOutlet posiblemente podría apuntar a un objeto que puede que ya no exista, debido a que lo haya liberado su propietario, o se haya liberado automáticamente en algún momento del ciclo de vida del progtwig. Retenerlo evita que ese objeto sea desasignado hasta que hayas terminado con él.

Los objetos en el archivo de punta se crean con un conteo 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 setter disponible o retiene el objeto de manera predeterminada si no hay un método setter disponible. 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 loadNibNamed: owner: options: method o los objetos dentro de la matriz para evitar que esos objetos se liberen prematuramente.

Bueno, en el segundo caso está agregando un método getter / setter para ese IBOutlet en particular. Cada vez que agrega un método getter / setter (que casi siempre) desea tener configurado para mantener para problemas de administración de memoria. Creo que una mejor manera de plantear tu pregunta hubiera sido esta:

 @interface RegisterController : UIViewController  { IBOutlet UITextField *usernameField; } @property (nonatomic) IBOutlet UITextField *usernameField; 

o

 @interface RegisterController : UIViewController  { IBOutlet UITextField *usernameField; } @property (nonatomic, retain) IBOutlet UITextField *usernameField; 

En ese caso, entonces sí, necesitaría agregar un retener, ya que afectará la administración de la memoria. Aunque puede no tener ningún efecto, si está agregando y eliminando de forma programática IBOutlet, podría tener problemas.

Como regla general: siempre agregue una propiedad @ (con retención) siempre que tenga una IBOutlet.