¿Dónde poner iVars en el objective “moderno” C?

El libro “iOS6 por Tutoriales” de Ray Wenderlich tiene un capítulo muy bueno sobre la escritura de código Objective-C más “moderno”. En una sección, los libros describen cómo mover iVars del encabezado de la clase al archivo de implementación. Como todos los iVars deberían ser privados, parece ser lo correcto.

Pero hasta ahora encontré 3 formas de hacerlo. Todos lo hacen de manera diferente.

1.) Coloque iVars debajo de @implementantion dentro de un bloque de llaves (así es como se hace en el libro).

2.) Ponga iVars bajo @implementacion sin bloque de llaves

3.) Ponga iVars dentro de la interfaz privada encima de @implementantion (una extensión de clase)

Todas estas soluciones parecen funcionar bien y hasta ahora no he notado ninguna diferencia en el comportamiento de mi aplicación. Supongo que no hay una forma “correcta” de hacerlo, pero necesito escribir algunos tutoriales y quiero elegir solo una forma para mi código.

¿A qué camino debería ir?

Editar: solo estoy hablando de iVars aquí. No propiedades Solo variables adicionales que el objeto necesita solo para sí mismo y que no deberían exponerse al exterior.

Ejemplos de código

1)

#import "Person.h" @implementation Person { int age; NSString *name; } - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

2)

 #import "Person.h" @implementation Person int age; NSString *name; - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

3)

 #import "Person.h" @interface Person() { int age; NSString *name; } @end @implementation Person - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

La capacidad de poner variables de instancia en el bloque @implementation , o en una extensión de clase, es una característica del “tiempo de ejecución moderno de Objective-C”, que es utilizado por todas las versiones de iOS, y por los progtwigs de Mac OS X de 64 bits.

Si desea escribir aplicaciones Mac OS X de 32 bits, debe poner las variables de su instancia en la statement @interface . No obstante, es probable que no necesite una versión de 32 bits de su aplicación. OS X ha admitido aplicaciones de 64 bits desde la versión 10.5 (Leopard), que se lanzó hace más de cinco años.

Entonces, supongamos que solo está escribiendo aplicaciones que usarán el tiempo de ejecución moderno. ¿Dónde deberías poner tus ivars?

Opción 0: en la @interface (Do not Do It)

Primero, veamos por qué no queremos poner variables de instancia en una statement @interface .

  1. Colocar variables de instancia en una @interface expone detalles de la implementación a los usuarios de la clase. Esto puede llevar a esos usuarios (¡incluso usted mismo al usar sus propias clases!) A confiar en los detalles de implementación que no deberían. (Esto es independiente de si declaramos los ivars @private ).

  2. Colocar variables de instancia en una @interface hace que la comstackción tome más tiempo, porque cada vez que agregamos, cambiamos o eliminamos una statement ivar, tenemos que volver a comstackr cada archivo .m que importe la interfaz.

Entonces no queremos poner variables de instancia en la @interface . ¿Dónde deberíamos ponerlos?

Opción 2: en @implementation without braces (Do not Do It)

A continuación, analicemos su opción 2, “Ponga iVars bajo @implementacion sin bloque de llaves”. ¡Esto no declara variables de instancia! Usted está hablando de esto:

 @implementation Person int age; NSString *name; ... 

Ese código define dos variables globales. No declara ninguna variable de instancia.

Está bien definir variables globales en su archivo .m , incluso en su @implementation , si necesita variables globales, por ejemplo, porque quiere que todas sus instancias compartan algún estado, como un caché. Pero no puedes usar esta opción para declarar ivars, porque no declara ivars. (Además, las variables globales privadas para su implementación generalmente deben declararse static para evitar contaminar el espacio de nombre global y poner en riesgo los errores de tiempo de enlace).

Eso deja tus opciones 1 y 3.

Opción 1: en @implementation con llaves (hazlo)

Usualmente queremos usar la opción 1: póngalos en su bloque @implementation principal, entre llaves, de esta manera:

 @implementation Person { int age; NSString *name; } 

Los colocamos aquí porque mantiene su existencia privada, evitando los problemas que describí anteriormente, y porque generalmente no hay razón para ponerlos en una extensión de clase.

Entonces, ¿cuándo queremos usar su opción 3, ponerlos en una extensión de clase?

Opción 3: en una extensión de clase (hazlo solo cuando sea necesario)

Casi nunca hay una razón para ponerlos en una extensión de clase en el mismo archivo que la @implementation implementación de la clase. También podríamos ponerlos en @implementation en ese caso.

Pero ocasionalmente podemos escribir una clase que sea lo suficientemente grande como para dividir su código fuente en varios archivos. Podemos hacer eso usando categorías. Por ejemplo, si implementamos UICollectionView (una clase bastante grande), podemos decidir que queremos poner el código que administra las colas de vistas reutilizables (celdas y vistas suplementarias) en un archivo fuente separado. Podríamos hacer eso separando esos mensajes en una categoría:

 // UICollectionView.h @interface UICollectionView : UIScrollView - (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @property (nonatomic, retain) UICollectionView *collectionViewLayout; // etc. @end @interface UICollectionView (ReusableViews) - (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier; - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier; - (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier; - (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier; - (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath; - (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath; @end 

Bien, ahora podemos implementar los métodos principales UICollectionView en UICollectionView.m y podemos implementar los métodos que administran vistas reutilizables en UICollectionView+ReusableViews.m , lo que hace que nuestro código fuente sea un poco más manejable.

Pero nuestro código de administración de vista reutilizable necesita algunas variables de instancia. Esas variables tienen que estar expuestas a la @implementation clase principal en UICollectionView.m , por lo que el comstackdor las emitirá en el archivo .o . Y también tenemos que exponer esas variables de instancia al código en UICollectionView+ReusableViews.m , por lo que esos métodos pueden usar los ivars.

Aquí es donde necesitamos una extensión de clase. Podemos poner los ivars de administración de vista reutilizable en una extensión de clase en un archivo de encabezado privado:

 // UICollectionView_ReusableViewsSupport.h @interface UICollectionView () { NSMutableDictionary *registeredCellSources; NSMutableDictionary *spareCellsByIdentifier; NSMutableDictionary *registeredSupplementaryViewSources; NSMutableDictionary *spareSupplementaryViewsByIdentifier; } - (void)initReusableViewSupport; @end 

No enviaremos este archivo de encabezado a los usuarios de nuestra biblioteca. Solo lo importaremos en UICollectionView.m y en UICollectionView+ReusableViews.m , para que todo lo que necesite ver estos ivars pueda verlos. También hemos incluido un método que queremos que init método init principal para inicializar el código de gestión de vistas reutilizables. Llamaremos a ese método desde -[UICollectionView initWithFrame:collectionViewLayout:] en UICollectionView.m , y lo implementaremos en UICollectionView+ReusableViews.m .

La opción 2 está completamente equivocada. Esas son variables globales, no variables de instancia.

Las opciones 1 y 3 son esencialmente idénticas. No hace absolutamente ninguna diferencia.

La elección es si colocar variables de instancia en el archivo de encabezado o el archivo de implementación. La ventaja de usar el archivo de cabecera es que tiene un atajo de teclado rápido y fácil (Comando + Control + Arriba en Xcode) para ver y editar las variables de instancia y la statement de interfaz.

La desventaja es que expone los detalles privados de su clase en un encabezado público. Eso no es deseable en algunos casos, particularmente si está escribiendo código para que otros lo usen. Otro problema potencial es que si está usando Objective-C ++, es bueno evitar poner cualquier tipo de datos C ++ en su archivo de encabezado.

Las variables de instancias de implementación son una gran opción para ciertas situaciones, pero para la mayoría de mi código todavía pongo las variables de instancia en el encabezado simplemente porque es más conveniente para mí como codificador que trabaja en Xcode. Mi consejo es hacer lo que creas que sea más conveniente para ti.

En gran medida tiene que ver con la visibilidad de la ivar a subclases. Las subclases no podrán acceder a las variables de instancia definidas en el bloque @implementation .

Para el código reutilizable que planeo distribuir (por ejemplo, código de biblioteca o framework) donde prefiero no exponer las variables de instancia para la inspección pública, entonces me inclino a colocar los ivars en el bloque de implementación (su opción 1).

Debe colocar variables de instancia en una interfaz privada encima de la implementación. Opción 3.

La documentación para leer sobre esto es la guía de Progtwigción en Objective-C .

De la documentación:

Puede definir variables de instancia sin propiedades

Es una buena práctica usar una propiedad en un objeto cada vez que necesite realizar un seguimiento de un valor u otro objeto.

Si necesita definir sus propias variables de instancia sin declarar una propiedad, puede agregarlas dentro de llaves en la parte superior de la interfaz o implementación de clase, como esta:

Los ivars públicos realmente deberían ser propiedades declaradas en la interfaz @ (probablemente en lo que estás pensando en 1). Ivars privados, si ejecuta el último Xcode y utiliza el tiempo de ejecución moderno (OS X de 64 bits o iOS), puede declararse en @implementation (2), en lugar de en una extensión de clase, que es probablemente lo que usted ‘ pensando en 3.