2010-05-17 11 views
64

Si uso objc_setAssociatedObject/objc_getAssociatedObject dentro de una implementación de categoría para almacenar una variable de instancia simulada en un método setter, ¿cómo accedería a la clave en el método getter ya que cualquier variable declarada en el método setter estaría fuera del alcance del método getter ?¿Cómo uso objc_setAssociatedObject/objc_getAssociatedObject dentro de un objeto?

Edit: Para aclarar, si tuviera que usar el siguiente patrón, ¿dónde debería declarar STRING_KEY para poder usarlo tanto en el método setter como getter?

@interface NSView (simulateVar) 
-(void)setSimualtedString:(NSString *)myString; 
-(NSString *)simulatedString; 
@end 

@implementation NSView (simulateVar) 

-(void)setSimualtedString: (NSString *)myString 
{ 
    objc_setAssociatedObject(self, &STRING_KEY, myString, OBJC_ASSOCIATION_RETAIN); 
} 

-(NSString *)simulatedString 
{ 
    return (NSString *)objc_getAssociatedObject(self, &STRING_KEY); 
} 

@end 

Respuesta

60

declarar una variable estática para que pueda utilizar su direccióncomo la clave. La llamada a objc_setAssociatedObject toma un void * y solo se usa la dirección de su variable estática, no el contenido de un NSString ... que solo está desperdiciando memoria.

Sólo tiene que añadir:

static char STRING_KEY; // global 0 initialization is fine here, no 
         // need to change it since the value of the 
         // variable is not used, just the address 
+0

la respuesta anterior hace referencia inicialmente a un enlace a Apple Docs que ya no existe. [Aquí] (https://github.com/alexzielenski/ZKRevealingTableViewCell/blob/master/vendor/ZKRevealingTableViewCell.m) es un ejemplo que ilustra la respuesta arriba, aunque no está en un contexto de categoría setter/getter. Si alguien tiene un mejor ejemplo, por favor comparte! Por alguna razón, el tema de los objetos asociados no se cubre ampliamente en la web. – abbood

+1

Creo que este es un excelente ejemplo de lo que el OP está preguntando: https://github.com/mystcolor/AFNetworking-ProxyQueue/blob/master/AFHTTPClient%2BProxyQueue.m#L149 – qix

+1

Otra buena tecla es un selector - He estado usando esto por un tiempo, leí sobre esto en algún blog (¿Mike Ash? ¿Mattt Thompson?). –

16

Declara una variable estática (alcance de la unidad de compilación) en el nivel superior del archivo de origen. Se puede ayudar a hacerlo significativo, algo como esto:

static NSString *MYSimulatedString = @"MYSimulatedString"; 
+0

editado para aclarar – leo

+0

He reemplazado mi respuesta con una respuesta a su pregunta, ahora sé lo que es :-) –

+0

Gracias! Esto parece bastante obvio. Parece que mi cerebro quiere encapsular todo :). – leo

21

Para el almacenamiento asociado void * llaves, me gusta esta manera de hacerlo:

static void * const kMyAssociatedStorageKey = (void*)&kMyAssociatedStorageKey; 

Esto evita tener otra cadena constante en el ejecutable, y por estableciendo su valor en la dirección de sí mismo, obtienes uniquing bueno, y el comportamiento const (por lo que nominalmente obtendrás un reclamo de compilador si haces algo que cambiaría el valor de la clave), sin ningún símbolo extra exportado innecesario.

9

Bastante cerca. Aquí hay un ejemplo completo.

.h-archivo

@interface NSObject (ExampleCategoryWithProperty) 

@property (nonatomic, retain) NSArray *laserUnicorns; 

@end 

.m-archivo

#import <objc/runtime.h> 

static void * LaserUnicornsPropertyKey = &LaserUnicornsPropertyKey; 

@implementation NSObject (ExampleCategoryWithProperty) 

- (NSArray *)laserUnicorns { 
    return objc_getAssociatedObject(self, LaserUnicornsPropertyKey); 
} 

- (void)setLaserUnicorns:(NSArray *)unicorns { 
    objc_setAssociatedObject(self, LaserUnicornsPropertyKey, unicorns, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 

@end 

Al igual que una propiedad normal, - accesible con punto-notación

NSObject *myObject = [NSObject new]; 
myObject.laserUnicorns = @[@"dipwit", @"dipshit"]; 
NSLog(@"Laser unicorns: %@", myObject.laserUnicorns); 

sintaxis más fácil

Como alternativa puede usar @selector(nameOfGetter) en lugar de crear un puntero estática. ¿Por qué? Ver https://stackoverflow.com/a/16020927/202451. Ejemplo:

- (NSArray *)laserUnicorns { 
    return objc_getAssociatedObject(self, @selector(laserUnicorns)); 
} 

- (void)setLaserUnicorns:(NSArray *)unicorns { 
    objc_setAssociatedObject(self, @selector(laserUnicorns), unicorns, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 
+0

¡La mejor respuesta! Solo uno que resolvió el problema para mí. –

36

sé que esta pregunta es dejar de fumar de edad, pero creo que para lo completo que hay otra manera de cómo utilizar objetos asociados vale la pena mencionar. Esta solución utiliza @selector y, por lo tanto, no hay necesidad de ninguna variable adicional o constante.

@interface NSObject (CategoryWithProperty) 

@property (nonatomic, strong) NSObject *property; 

@end 

@implementation NSObject (CategoryWithProperty) 

- (NSObject *)property { 
    return objc_getAssociatedObject(self, @selector(property)); 
} 

- (void)setProperty:(NSObject *)value { 
    objc_setAssociatedObject(self, @selector(property), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 

@end 

(inspirado en http://www.tuaw.com/2013/04/10/devjuice-better-objective-c-associated-objects/)

+2

¡Gracias por esta solución! Parece ser el más claro para mí. –

+0

Agradable y elegante, gracias! – mojuba

+0

Esta debería ser la respuesta correcta. Mucho mejor ! – joan

5

La respuesta aceptada ya lo hizo bien, pero me gustaría añadir una explicación: el punto de la "llave" es conseguir un único (por proceso/programa) identificador que no tendrá ninguna colisión accidental.

imaginar la clave que se está declarada como NSUInteger: mucha gente utiliza los valores estándar como 0, 1 o 42. Especialmente si está utilizando alguna biblioteca o marco, es probable que haya colisiones.

Así que, en cambio, un ingenioso ingeniero de Apple tuvo la idea de declarar la clave como un puntero con la intención de declarar una variable y pasar el puntero a esa variable como clave. El vinculador asignará a esa variable una dirección que es única dentro de su aplicación y por lo tanto, se garantiza que obtendrá un valor único libre de colisiones.

De hecho, puede pasar cualquier valor que desee, el puntero no se desreferencia (lo he probado). Pasando (void *)0 o (void *)42, la clave funciona, aunque no es una buena idea (así que no lo haga). Para objc_set/getAssociatedObject, todo lo que importa es que el valor pasado como clave es único dentro de su proceso/programa.

Cuestiones relacionadas