iphone - Declaración/definición de ubicaciones de variables en ObjectiveC?
objective-c ios (4)
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 de iNotVisible no son visibles desde ninguna otra clase. Este es un problema de visibilidad, por lo que declararlos con @property
o @public
no lo cambia.
Dentro de un constructor es una buena práctica acceder a las variables declaradas con @property
usando underscore en self
lugar para evitar los 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 utilizando 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 respaldo ivar _iNotVisible2
que es privado para la subclase. En Objective-C se pueden leer o establecer todas las variables, incluso aquellas marcadas como @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 discutí los atributos de gestión de memoria o los atributos readonly / readwrite, getter / setter.
Desde que empecé a trabajar en las aplicaciones de iOS y el objetivo C, me he quedado realmente perplejo con las diferentes ubicaciones en las que se podrían declarar y definir variables. Por un lado tenemos el enfoque tradicional de C, por el otro tenemos las nuevas directivas de ObjectiveC que agregan OO además de eso. ¿Podrían ustedes ayudarme a entender las mejores prácticas y situaciones en las que me gustaría utilizar 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 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 estáticos basados en variables antes. ¿Hay otros usos convenientes que me falta?
- Lo que tomé al trabajar con iOS es que los ivars han desaparecido completamente por fuera de la directiva @synthesize y, por lo tanto, se pueden ignorar en su mayoría. Es ese el caso?
- En cuanto a 5: ¿por qué querría declarar métodos en interfaces privadas? Mis métodos de clase privados parecen compilar muy bien sin una declaración en la interfaz. ¿Es principalmente para legibilidad?
Muchas gracias, amigos!
Primero, lee la respuesta de @ DrummerB. Es una buena visión general de los por qué y lo que generalmente debe hacer. 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 sabes exactamente lo que estás haciendo, pero nunca lo haces). Puede definir varios otros tipos de cosas:
- typdefs
- enums
- Externo
Los modelos se parecen a declaraciones variables, pero son solo una promesa de declararlo realmente en otro lugar. En ObjC, solo deben usarse para declarar constantes, y generalmente solo constantes de cadena. Por ejemplo:
extern NSString * const MYSomethingHappenedNotification;
En su archivo .m
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 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 archivo pueden ir aquí. Estos son el equivalente de las variables de clase en otros idiomas.
@interface SampleClass()
// 5) private interface, can define private methods and properties here
@end
Sí
@implementation SampleClass
{
// 6) define ivars
}
Pero muy raramente. Casi siempre debes permitir que clang (Xcode) cree las variables por ti. Las excepciones suelen ser alrededor de ivars no ObjC (como objetos Core Foundation, y especialmente objetos C ++ si esta es una clase ObjC ++), o ivars que tienen semántica de almacenamiento extraño (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, no deberías @sintetizar más. 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 épocas diferentes (ABI frágil, ABI no frágil, ABI no frágil + auto sintetizador). Entonces, cuando ves el código anterior, puede ser un poco confuso. Así la confusión que surge de la simplicidad: D
Puedo entender tu confusión. Especialmente dado que las actualizaciones recientes de Xcode y el nuevo compilador de LLVM cambiaron la forma en que se pueden declarar los ivars y las propiedades.
Antes del Objective-C "moderno" (en el "viejo" Obj-C 2.0) no tenías muchas opciones. Las variables de instancia solían declararse en el encabezado entre llaves { }
:
// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
@end
Pudo acceder a estas variables solo en su implementación, pero no desde otras clases. Para hacerlo, 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 forma, también pudo obtener y establecer esta variable de instancia de otras clases, utilizando la sintaxis 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];
Debido a que declarar e implementar manualmente cada método de acceso fue bastante molesto, se introdujeron @property
y @synthesize
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 corto. Los métodos de acceso se implementarán para usted y aún puede usar la sintaxis de paréntesis como antes. Pero además, también puedes 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 tienes que declarar una variable de instancia y también puedes omitir @synthesize
. Si no declara un ivar, el compilador lo agregará para usted y también generará los métodos de acceso sin tener que usar @synthesize
.
El nombre predeterminado para el ivar generado automáticamente es el nombre o su propiedad que comienza con un guión bajo. Puede cambiar el nombre de @synthesize myVar = iVarName;
utilizando @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 dado 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 bloque @interface
en el archivo de implementación es en realidad una Extension 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 propiedad de readonly
en su encabezado.
@property (nonatomic, readonly) myReadOnlyVar;
y redeclare en su archivo de implementación como readwrite
para poder establecerlo usando la sintaxis de la propiedad y no solo a través del acceso directo al ivar.
En cuanto a declarar las variables completamente fuera de cualquier @interface
o @implementation
block, sí, esas son variables simples de C y funcionan exactamente igual.
También soy bastante nuevo, así que espero no arruinar nada.
1 y 4: variables globales de estilo C: tienen un amplio alcance de archivo. La diferencia entre los dos es que, dado que tienen un ancho de 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 los descriptores de acceso utilizando propiedades, ya que hacen que la administración de la memoria sea agradable y simple, además de proporcionar una notación de puntos fácil de entender.
6: Los ivars de implementación son algo nuevos. Es un buen lugar para poner ivars privados, ya que solo quiere exponer lo que se necesita en el encabezado público, pero las subclases no los heredan de AFAIK.
3 y 7: Método público y declaraciones de propiedades, luego implementaciones.
5: interfaz privada. Siempre uso interfaces privadas siempre que puedo para mantener las cosas limpias y crear un tipo de efecto de caja negra. Si no necesitan saberlo, póngalo allí. También lo hago por legibilidad, no sé si hay otras razones.