Declaración / definición de ubicaciones de variables en ObjectiveC?

113

Desde que comencé a trabajar en aplicaciones iOS y el objetivo C, me han desconcertado mucho las diferentes ubicaciones donde se podrían declarar y definir variables. Por un lado tenemos el enfoque tradicional de C, por el otro tenemos las nuevas directivas ObjectiveC que agregan OO encima de eso. ¿Podrían ayudarme a comprender las mejores prácticas y las situaciones en las que me gustaría usar estas ubicaciones para mis variables y quizás corregir mi comprensión actual?

Aquí hay una clase de muestra (.h y .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

y

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • Mi comprensión de 1 y 4 es que esas son declaraciones y definiciones basadas en archivos de estilo C que no comprenden en absoluto el concepto de clase y, por lo tanto, deben usarse exactamente como se usarían en C.Las he visto utilizado para implementar singletons basados ​​en variables estáticas antes. ¿Hay otros usos convenientes que me falta?
  • Mi opinión al trabajar con iOS es que los ivars se han eliminado casi por completo fuera de la directiva @synthesize y, por lo tanto, se pueden ignorar en su mayoría. ¿Es ese el caso?
  • Con respecto a 5: ¿por qué querría declarar métodos en interfaces privadas? Mis métodos de clase privada parecen compilarse bien sin una declaración en la interfaz. ¿Es principalmente por legibilidad?

¡Muchas gracias, amigos!

Alexandr Kurilin
fuente

Respuestas:

154

Puedo entender tu confusión. Especialmente desde que las actualizaciones recientes de Xcode y el nuevo compilador LLVM cambiaron la forma en que se pueden declarar los ivars y las propiedades.

Antes del "moderno" Objective-C (en el "antiguo" Obj-C 2.0) no tenías muchas opciones. Las variables de instancia solían declararse en el encabezado entre corchetes { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Pudiste acceder a estas variables solo en tu implementación, pero no desde otras clases. Para hacer eso, tenía que declarar métodos de acceso, que se parecen a esto:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

De esta manera, también pudo obtener y establecer esta variable de instancia de otras clases, utilizando la sintaxis habitual de corchetes para enviar mensajes (métodos de llamada):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Porque declarar e implementar manualmente cada método de acceso era bastante molesto, @propertyy @synthesizese introdujeron para generar automáticamente los métodos de acceso:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

El resultado es un código mucho más claro y más corto. Los métodos de acceso se implementarán para usted y aún puede usar la sintaxis de corchetes como antes. Pero además, también puede usar la sintaxis de puntos para acceder a las propiedades:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Desde Xcode 4.4, ya no tiene que declarar una variable de instancia usted mismo y también puede omitir @synthesize. Si no declara un ivar, el compilador lo agregará por usted y también generará los métodos de acceso sin que tenga que usar @synthesize.

El nombre predeterminado para el ivar generado automáticamente es el nombre de su propiedad que comienza con un guión bajo. Puede cambiar el nombre del ivar generado usando@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

Esto funcionará exactamente como el código anterior. Por razones de compatibilidad, aún puede declarar ivars en el encabezado. Pero debido a que la única razón por la que querría hacer eso (y no declarar una propiedad) es crear una variable privada, ahora también puede hacerlo en el archivo de implementación y esta es la forma preferida.

Un @interfacebloque en el archivo de implementación es en realidad una extensión y se puede usar para reenviar métodos de declaración (ya no es necesario) y para (re) declarar propiedades. Por ejemplo, podría declarar una readonlypropiedad en su encabezado.

@property (nonatomic, readonly) myReadOnlyVar;

y vuelva a declararlo en su archivo de implementación readwritepara poder configurarlo usando la sintaxis de la propiedad y no solo a través del acceso directo al ivar.

En cuanto a declarar variables completamente fuera de cualquier bloque @interfaceo @implementation, sí, esas son variables C simples y funcionan exactamente igual.

BateristaB
fuente
2
¡gran respuesta! También tenga en cuenta: stackoverflow.com/questions/9859719/…
nycynik
44

Primero, lea la respuesta de @ DrummerB. Es una buena descripción de los porqués y lo que debe hacer en general. Con eso en mente, a sus preguntas específicas:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

No hay definiciones de variables reales aquí (es técnicamente legal hacerlo si sabe exactamente lo que está haciendo, pero nunca lo haga). Puede definir varios otros tipos de cosas:

  • typdefs
  • enumeraciones
  • Externos

Los externos parecen declaraciones de variables, pero son solo una promesa de declararlos en otro lugar. En ObjC, solo deben usarse para declarar constantes y, en general, solo constantes de cadena. Por ejemplo:

extern NSString * const MYSomethingHappenedNotification;

Luego, en su .marchivo declararía la constante real:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Como señaló DrummerB, esto es un legado. No pongas nada aquí.


// 3) class-specific method / property declarations

@end

Sí.


#import "SampleClass.h"

// 4) what goes here?

Constantes externas, como se describe arriba. También las variables estáticas de archivos pueden ir aquí. Son el equivalente a las variables de clase en otros lenguajes.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end


@implementation SampleClass
{
    // 6) define ivars
}

Pero muy raramente. Casi siempre debes permitir que clang (Xcode) cree las variables por ti. Las excepciones generalmente son alrededor de ivars que no son de ObjC (como objetos Core Foundation, y especialmente objetos C ++ si se trata de una clase ObjC ++), o ivars que tienen una semántica de almacenamiento extraña (como ivars que no coinciden con una propiedad por alguna razón).


// 7) define methods and synthesize properties from both public and private
//    interfaces

En general, ya no deberías @synthesize. Clang (Xcode) lo hará por ti, y deberías dejarlo.

En los últimos años, las cosas se han vuelto dramáticamente más simples. El efecto secundario es que ahora hay tres eras diferentes (ABI frágil, ABI no frágil, ABI no frágil + auto-síntesis). Entonces, cuando vea el código anterior, puede resultar un poco confuso. Así, la confusión que surge de la simplicidad: D

Rob Napier
fuente
Me pregunto, pero ¿por qué no deberíamos sintetizar explícitamente? Lo hago porque encuentro mi código más fácil de entender, especialmente cuando algunas propiedades tienen descriptores de acceso sintetizados y algunas implementaciones personalizadas, ya que estoy acostumbrado a sintetizar. ¿Existe algún inconveniente en la síntesis explícita?
Metabble
El problema de usarlo como documentación es que realmente no documenta nada. A pesar de usar Synthesize, es posible que haya anulado uno o ambos accesos. No hay forma de distinguir de la línea de síntesis algo realmente útil. Lo único peor que no tener documentación es una documentación engañosa. Déjalo afuera.
Rob Napier
3
¿Por qué el número 6 es raro? ¿No es esta la forma más fácil de obtener una variable privada?
pfrank
La forma más fácil y mejor de obtener una propiedad privada es la # 5.
Rob Napier
1
@RobNapier Todavía es necesario usar @ sintetizar a veces (por ejemplo, si una propiedad es de solo lectura tiene su acceso anulado)
Andy
6

También soy bastante nuevo, así que espero no estropear nada.

1 y 4: Variables globales de estilo C: tienen un alcance de archivo amplio. La diferencia entre los dos es que, dado que tienen todo el archivo, el primero estará disponible para cualquiera que importe el encabezado, mientras que el segundo no.

2: variables de instancia. La mayoría de las variables de instancia se sintetizan y recuperan / configuran a través de accesos usando propiedades porque hace que la administración de la memoria sea agradable y simple, así como también le brinda una notación de puntos fácil de entender.

6: Los ivars de implementación son algo nuevos. Es un buen lugar para colocar ivars privados, ya que solo desea exponer lo que se necesita en el encabezado público, pero las subclases no los heredan AFAIK.

3 y 7: Declaraciones públicas de métodos y propiedades, luego implementaciones.

5: Interfaz privada. Siempre uso interfaces privadas siempre que puedo para mantener las cosas limpias y crear una especie de efecto de caja negra. Si no necesitan saberlo, póngalo allí. También lo hago por legibilidad, no sé si hay otras razones.

Metabble
fuente
1
No creas que arruinaste nada :) Algunos comentarios - # 1 y # 4 especialmente con # 4 a menudo ves variables de almacenamiento estáticas. # 1 a menudo verá el almacenamiento externo especificado y luego el almacenamiento real asignado en # 4. # 2) solo generalmente si una subclase lo necesita por cualquier motivo. # 5 ya no es necesario reenviar declarar métodos privados.
Carl Veazey
Sí, yo mismo revisé la declaración anticipada. Solía ​​dar una advertencia si un método privado llamaba a otro que estaba definido después de él sin una declaración directa, ¿verdad? Me sorprendió un poco cuando no me advirtió.
Metabble
Sí, es una parte nueva del compilador. Realmente han hecho muchos avances últimamente.
Carl Veazey
6

Este es un ejemplo de todo tipo de variables declaradas en Objective-C. El nombre de la variable indica su acceso.

Archivo: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

Archivo: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Tenga en cuenta que las variables iNotVisible no son visibles desde ninguna otra clase. Este es un problema de visibilidad, por lo que declararlos con @propertyo @publicno lo cambia.

Dentro de un constructor, es una buena práctica acceder a las variables declaradas con @propertysubrayado en su lugar selfpara evitar efectos secundarios.

Intentemos acceder a las variables.

Archivo: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

Archivo: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Todavía podemos acceder a las variables no visibles usando el tiempo de ejecución.

Archivo: Cow.m (parte 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Intentemos acceder a las variables no visibles.

Archivo: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Esto imprime

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Tenga en cuenta que pude acceder al ivar de respaldo _iNotVisible2que es privado para la subclase. En Objective-C se pueden leer o configurar todas las variables, incluso aquellas que están marcadas @private, sin excepciones.

No incluí objetos asociados o variables C ya que son aves diferentes. En cuanto a las variables C, cualquier variable definida fuera de @interface X{}o @implementation X{}es una variable C con alcance de archivo y almacenamiento estático.

No hablé de los atributos de administración de memoria, ni de los atributos de solo lectura / lectura y escritura, captador / configurador.

Jano
fuente