2009-08-03 17 views
53

En la mayoría de los ejemplos que ver la siguiente configuración de IBOutlets:¿Necesita un IBOutlet ser una propiedad y sintetizado?



(Example A) 

FooController.h: 

@interface FooController : UIViewController { 
    UILabel *fooLabel; 
} 

@property (nonatomic, retain) IBOutlet UILabel *fooLabel; 

@end 

FooController.m: 

@implementation FooController 

@synthesize fooLabel; 

@end 

Pero esto también funciona muy bien (aviso: sin propiedad y sin sintetizar):



(Example B) 

FooController.h: 

@interface FooController : UIViewController { 
    IBOutlet UILabel *fooLabel; 
} 

@end 

FooController.m: 

@implementation FooController 

@end 

¿Hay algunas desventajas de definir IBOutlets como en el Ejemplo B? ¿Como pérdidas de memoria? Parece funcionar bien y prefiero no exponer las IBOutlets como propiedades públicas ya que no se utilizan como tales, solo se usan en la implementación del controlador. Definirlo en tres lugares sin una necesidad real no me parece muy SECO (No repetir).

Respuesta

94

En Mac OS X, IBOutlets están conectados de esta manera:

  1. Busque un método llamado establece <OutletName>:. Si existe llámalo.
  2. Si no existe ningún método, busque una variable de instancia llamada <OutletName>, configúrela sin conservar.

En el iPhone OS, IBOutlets están conectados de esta manera:

  1. llamada [objeto setValue: outletValue forKey: @ "<OutletName>"]

El comportamiento del valor establecido para la clave es hacer algo como esto:

  1. Busque un método llamado conjunto <OutletName>:. Si existe llámalo.
  2. Si no existe un método, busque una variable de instancia llamada <OutletName>, configúrela y consérvela.

Si utiliza una propiedad, te vas a caer en el "Busque un método llamado conjunto <OutletName>: ..." caso en ambas plataformas. Si solo usa una variable de instancia, tendrá un comportamiento distinto de retención/liberación en Mac OS X VS iPhone OS. No hay nada de malo con el uso de una variable de instancia, solo necesita lidiar con esta diferencia de comportamiento cuando cambia de plataforma.

Aquí hay un enlace a la documentación completa sobre este tema. http://developer.apple.com/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW6

+0

Hola Jon, ¡Gracias por la respuesta detallada! Muy útil –

+0

¿Qué sucede si el nombre de la variable es diferente del nombre de la propiedad? ¿Importa si es diferente? –

+0

El nombre anterior "OutletName" se define como lo que está junto a la palabra clave "IBOutlet" en el código fuente. Si IBOutlet está en @property, no importa el nombre de la variable de instancia dado que se encontrará un setter. Si por alguna razón no existiera un colocador, se produciría una excepción al conectar el tomacorriente. Si la palabra clave IBOutlet está en la variable de instancia, y existe un setter con un nombre que no coincide, no se llamará al setter. –

4

el resultado final es exactamente el mismo, pero hay que tener algunas cosas en cuenta:

  • Al utilizar campos de instancia como puntos de venta, no se debe liberarlos en dealloc.

  • Al utilizar las propiedades que tienen el atributo (retener), usted tiene que liberar la propiedad en dealloc (usando self.property=nil o mediante la liberación de la variable de respaldo). Esto hace que sea mucho más transparente en cuanto a lo que está pasando.

En realidad todo se reduce a la misma vieja regla: "liberación harás lo que alloc/retener". Entonces, en caso de que use un campo de instancia como salida, no lo haya alojado/retenido, por lo que no debería liberarlo.

+5

Este consejo es correcto para Mac OS X, pero no para el sistema operativo del iPhone. Ver mi respuesta a continuación. –

+1

Llamar "self.property = nil" en un método dealloc no es una buena práctica para entrar. No debe llamar a los métodos desde init o dealloc; si está subclasificado, su subclase probablemente no espera que esos setters se llamen durring después de su liberación, o antes de que se inicie. –

+0

Es la única forma de liberar propiedades de retención automática que usan síntesis de variable de instancia (sin ninguna declaración explícita de campos de respaldo). No tienes elección, buena práctica o no. –

12

En Mac OS X, IBOutlets no se conservan por defecto. Esto es lo contrario del comportamiento en el sistema operativo iPhone: en el sistema operativo iPhone, si no declaras una propiedad, se conserva y debes liberar esta propiedad en el método dealloc. Además, el tiempo de ejecución de 64 bits puede sintetizar variables de instancia utilizando declaraciones de propiedades. Eso significa que algún día las variables de instancia (con IBOutlet) pueden omitirse.

Por estas razones, es más homogéneo y compatible crear siempre una propiedad y usar el IBOutlet solo en la propiedad. Desafortunadamente, también es más detallado.

En su primer ejemplo, siempre debe soltar la toma en el método dealloc. En su segundo ejemplo, debe liberar la salida solo con iPhone OS.

1

Es posible que esos ejemplos usen la retención porque el código de muestra asigna e inicializa programáticamente un UILabel y luego lo agrega a UIView. Ese es el caso de muchos ejemplos, ya que aprender a usar Interface Builder a menudo no es su objetivo.

El segundo ejemplo (sin propiedad y sin sintetizar) con IBOutlet se utiliza cuando el desarrollador 'asigna' el UILabel (botón, vista, etc.) dentro del Interface Builder, arrastrando el IBOulet hasta la etiqueta u otra vista. componente. En mi opinión, la acción anterior de arrastrar y soltar (Etiqueta en vista) también agrega la subvista, la Etiqueta a una vista, y así sucesivamente. La etiqueta es retenida por una Vista; una Vista es retenida por Window; La ventana es retenida por el propietario del archivo. El propietario del archivo suele ser su documento que se inicia en principal.

Usted notará que cuando paso a través de su programa (mediante la adición de un awakeFromNib

- (void)awakeFromNib 
{ 
    [fooLabel blahblah]; 
} 

que fooLabel ya tiene una dirección de memoria.

Eso es debido a que la etiqueta se ha inicializado de un lote de archivos (el archivo nib) utilizando no init pero initWithCoder. Esto esencialmente deserializa el filestream a un objeto y luego establece la variable IBOutlet. (Todavía estamos hablando del método IBOutlet)

También tenga en cuenta que el iOS mencionado anteriormente thod utiliza el método de valor clave

call [object setValue:outletValue forKey:@"<OutletName>"] 

que es el patrón Observer/Observable. Ese patrón requiere que el Objeto Observable haga referencia a cada Observer en un Conjunto/Matriz. Un cambio de valor repetirá el Conjunto/Matriz e igualmente actualizará todos los Observadores. Ese conjunto ya retendrá a cada observador por lo tanto la falta de retención en iOS.

Además y el resto es especulación.

Parece que los casos cuando se hace uso de Interface Builder entonces

@property (nonatomic, retain) IBOutlet UILabel *fooLabel; 

posiblemente se debe cambiar a

@property (nonatomic, weak) IBOutlet UILabel *fooLabel; 

o @property (no atómica, asignar) IBOutlet UILabel * fooLabel;

Y luego no necesita ser lanzado en un método dealloc. Además, satisfará los requisitos de OSX e iOS.

Eso está basado en la lógica y podría faltar algunas piezas aquí.

Sin embargo, puede no importar si la vista es persistente durante la vida de su programa. Mientras que una etiqueta en un cuadro de diálogo modal (abrir, cerrar, abrir, cerrar) de hecho puede haber retenido demasiado y tener fugas por ciclo. Y eso es porque (especulación otra vez) cada cuadro de diálogo cerrado se serializa en un sistema de archivos y por lo tanto persiste x, y posición y tamaño, junto con sus subvistas, etc. Y posteriormente se deserializa ... en la siguiente sesión abierta (Opuesto a decir minimiz u oculto.)

Cuestiones relacionadas