objective c - ¿Cómo uso objc_setAssociatedObject/objc_getAssociatedObject dentro de un objeto?
objective-c categories (6)
Declare una variable estática (alcance de la unidad de compilación) en el nivel superior del archivo de origen. Puede ayudar a que sea significativo, algo como esto:
static NSString *MYSimulatedString = @"MYSimulatedString";
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?
Editar: 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 en 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
Declare una variable estática para que pueda usar su dirección como la clave. La llamada a objc_setAssociatedObject toma un vacío * y solo se usa la dirección de su variable estática, no el contenido de un NSString ... que solo está desperdiciando memoria.
Solo necesitas agregar:
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
La respuesta aceptada ya salió bien, pero me gustaría añadir una explicación: el objetivo de la "clave" es obtener un identificador único (por proceso / programa) que no tenga colisiones accidentales.
Imagine que la clave se declara NSUInteger
: muchas personas usarían valores estándar como 0
, 1
o 42
. Especialmente si está utilizando alguna biblioteca o marco, es probable que haya colisiones.
Entonces, en cambio, un ingenioso ingeniero de Apple tuvo la idea de declarar que la clave era un puntero con la intención de declarar una variable y pasar el puntero a esa variable como una 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
como la clave funciona, aunque no es una buena idea (así que por favor no hagas eso). Para objc_set/getAssociatedObject
, todo lo que importa es que el valor pasado como clave es único dentro de su proceso / programa.
Muy cerca. Aquí hay un ejemplo completo.
.h-file
@interface NSObject (ExampleCategoryWithProperty)
@property (nonatomic, retain) NSArray *laserUnicorns;
@end
.m-file
#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 notación de puntos
NSObject *myObject = [NSObject new];
myObject.laserUnicorns = @[@"dipwit", @"dipshit"];
NSLog(@"Laser unicorns: %@", myObject.laserUnicorns);
Sintaxis más fácil
Alternativamente, puede usar @selector(nameOfGetter)
lugar de crear un puntero estático. ¿Por qué? Ver https://.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);
}
Para las claves de void *
almacenamiento asociadas, me gusta esta forma de hacerlo:
static void * const kMyAssociatedStorageKey = (void*)&kMyAssociatedStorageKey;
Esto evita tener otra cadena constante en el ejecutable, y al establecer su valor a la dirección de sí mismo, obtienes una buena uniquing y un comportamiento directo (por lo que obtendrás una queja del compilador si haces algo que cambiaría el valor del clave), sin símbolos adicionales innecesarios exportados.
Sé que esta pregunta ya dejó de funcionar, pero creo que, para completar, hay otra manera de utilizar los objetos asociados que 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/ )