¿Dónde poner iVars en Objective-C “moderno”?

81

El libro "iOS6 by Tutorials" de Ray Wenderlich tiene un capítulo muy agradable sobre cómo escribir 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. Dado que todas las iVars deberían ser privadas, esto parece ser lo correcto.

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

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

2.) Ponga iVars en @implementantion sin bloque de llaves

3.) Coloque iVars dentro de la interfaz privada sobre @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 solo quiero elegir una forma para mi código.

¿Qué camino debo tomar?

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

Muestras 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
TalkingCode
fuente
4
No hay comentarios sobre "correcto", pero otra opción, hacia la que parece que me estoy moviendo, es deshacerse de iVars por completo y convertir todo en una propiedad (principalmente dentro de una extensión de clase en el archivo .m). La coherencia me evita pensar en los detalles de implementación del estado.
Phillip Mills
1
Esta pregunta realmente beneficiaría a otros si actualizara la pregunta para mostrar ejemplos de código real que demuestren cada una de las 3 opciones. Personalmente, solo uso la opción 1, he visto la opción 3, pero no estoy familiarizado con la opción 2.
rmaddy
1
Gracias maddy. Agregué un código de muestra.
TalkingCode
4
Lo siento, acabo de notar la actualización. La opción 2 no es válida. Eso no crea ivars. Eso crea variables globales de archivo. Cada instancia de esa clase compartirá ese conjunto de variables. Puede utilizar variables de clase, no variables de instancia.
rmaddy

Respuestas:

162

La capacidad de poner variables de instancia en el @implementationbloque, 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 programas Mac OS X de 64 bits.

Si desea escribir aplicaciones Mac OS X de 32 bits, debe poner las variables de instancia en el @interface declaración. Sin embargo, es probable que no necesite admitir 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 el @interface(No lo hagas)

Primero, repasemos por qué no queremos poner variables de instancia en una @interfacedeclaración.

  1. Poner variables de instancia en un @interfaceexpone los detalles de la implementación a los usuarios de la clase. Esto puede llevar a esos usuarios (¡incluso a usted mismo cuando usa sus propias clases!) A confiar en detalles de implementación que no deberían. (Esto es independiente de si declaramos los ivars@private ).

  2. Poner variables de instancia en una @interfacehace que la compilación tome más tiempo, porque cada vez que agregamos, cambiamos o eliminamos una declaración ivar, tenemos que recompilar cada .marchivo que importa la interfaz.

Por lo tanto, no queremos poner variables de instancia en @interface. ¿Dónde debemos ponerlos?

Opción 2: @implementationSin tirantes (No lo hagas)

A continuación, analicemos su opción 2, "Ponga iVars en @implementantion sin bloque de llaves". ¡Esto no declara variables de instancia! Estás 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 .marchivo, incluso en su @implementation, si necesita variables globales, por ejemplo, porque desea que todas sus instancias compartan algún estado, como un caché. Pero no puede usar esta opción para declarar ivars, porque no declara ivars. (Además, las variables globales privadas para su implementación generalmente deben declararse staticpara evitar contaminar el espacio de nombres global y correr el riesgo de errores de tiempo de enlace).

Eso deja tus opciones 1 y 3.

Opción 1: En el @implementation con tirantes (Hazlo)

Por lo general, queremos usar la opción 1: colóquelos en su @implementationbloque principal , entre llaves, así:

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

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

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

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

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

Pero de vez en cuando podemos escribir una clase que sea lo suficientemente grande como para querer dividir su código fuente en varios archivos. Podemos hacer eso usando categorías. Por ejemplo, si estuviéramos implementando UICollectionView(una clase bastante grande), podríamos decidir que queremos poner el código que administra las colas de vistas reutilizables (celdas y vistas complementarias) 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 UICollectionViewmétodos principales en UICollectionView.my podemos implementar los métodos que administran vistas reutilizables enUICollectionView+ReusableViews.m , lo que hace que nuestro código fuente sea un poco más manejable.

Pero nuestro código de administración de vistas reutilizable necesita algunas variables de instancia. Esas variables deben estar expuestas a la clase principal @implementationen UICollectionView.m, por lo que el compilador las emitirá en el .oarchivo. Y también necesitamos exponer esas variables de instancia al código en UICollectionView+ReusableViews.m, para que esos métodos puedan usar los ivars.

Aquí es donde necesitamos una extensión de clase. Podemos poner los ivars de administración de vistas reutilizables 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. Simplemente lo importaremos dentro UICollectionView.my fuera UICollectionView+ReusableViews.m, para que todo lo que necesite ver estos ivars pueda verlos. También hemos incluido un método al que queremos initque llame el método principal para inicializar el código de administración de vistas reutilizable. Llamaremos a ese método desde -[UICollectionView initWithFrame:collectionViewLayout:]adentro UICollectionView.my lo implementaremos en UICollectionView+ReusableViews.m.

Rob Mayoff
fuente
4
Opción 3: también se puede usar cuando 1) desea hacer cumplir el uso de propiedades para sus ivars, incluso dentro de su propia implementación (@property int age), y 2) desea anular una propiedad pública de solo lectura (@property (readonly) int age) en su encabezado para permitir que su código acceda a la propiedad como readwrite en la implementación (@property (readwrite) int age). A menos que haya otras formas de hacer esto, @rob mayoff?
leanne
@leanne Si desea anular una readonlypropiedad para que sea de forma privada readwrite, debe usar una extensión de clase. Pero la pregunta era dónde colocar los ivars, no dónde colocar las declaraciones de propiedad. No veo cómo el uso de una extensión de clase puede evitar que la implementación acceda a ivars. Para hacer eso, necesitaría poner las implementaciones de su método en una categoría, porque el compilador debe ver todas las extensiones de clase que declaran ivar antes de ver la extensión @implementation.
Rob Mayoff
@rob mayoff, verdadero y verdadero: haces puntos válidos. Holli especificó que las propiedades no estaban en duda aquí, y "hacer cumplir" fue un poco fuerte de mi parte con respecto a su uso. Independientemente, si uno quiere usar propiedades para acceder a sus ivars, en lugar de hacerlo directamente, esa sería la forma de hacerlo. Y, por supuesto, aún puede acceder a los ivars directamente usando '_', (_age = someAge). Agregué el comentario porque pensé que vale la pena mencionar el uso de la propiedad cuando se habla de ivars, ya que muchas personas los usan juntos, y esta sería la manera de hacerlo.
leanne
Esa es la "Opción 0: En la interfaz @ (No lo hagas)".
rob mayoff
5

La opción 2 es completamente incorrecta. 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 poner variables de instancia en el archivo de encabezado o en el archivo de implementación. La ventaja de usar el archivo de encabezado es que tiene un atajo de teclado rápido y fácil (Comando + Control + Arriba en Xcode) para ver y editar sus variables de instancia y declaración de interfaz.

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

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

Darren
fuente
4

En gran parte, tiene que ver con la visibilidad del ivar a las subclases. Las subclases no podrán acceder a las variables de instancia definidas en el @implementationbloque.

Para el código reutilizable que planeo distribuir (por ejemplo, código de biblioteca o marco) 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).

FluffulousChimp
fuente
3

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

La documentación para leer sobre esto es la guía Programació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 entre llaves en la parte superior de la interfaz o implementación de la clase, así:

JoePasq
fuente
1
Estoy de acuerdo contigo: si ibas a definir ivar, la extensión de clases privadas es el mejor lugar. Curiosamente, sin embargo, la documentación a la que hace referencia no solo no toma una posición sobre dónde se debe definir ivars (simplemente presenta las tres alternativas), sino que va más allá y recomienda que uno no debe usar ivars en absoluto, sino más bien "es una buena práctica utilizar una propiedad". Ese es el mejor consejo que he visto hasta ahora.
Rob
1

Los ivars públicos realmente deberían ser propiedades declaradas en @interface (probablemente lo que estás pensando en 1). Los ivars privados, si está ejecutando el último Xcode y utiliza el tiempo de ejecución moderno (OS X o iOS de 64 bits), se pueden declarar en @implementation (2), en lugar de en una extensión de clase, que es probablemente lo que ' estoy pensando en 3.

Jon Shier
fuente