objective c - Objective-C: variable de propiedad/instancia en categoría
category (6)
Como no puedo crear una propiedad sintetizada en una Categoría en Objective-C, no sé cómo optimizar el siguiente código:
@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end
@implementation MyClass (Variant)
@dynamic test;
- (NSString *)test {
NSString *res;
//do a lot of stuff
return res;
}
@end
El método de prueba se llama varias veces en el tiempo de ejecución y estoy haciendo muchas cosas para calcular el resultado. Normalmente, usando una propiedad sintetizada, almaceno el valor en una prueba de IVar la primera vez que se llama al método, y solo devuelvo este IVar la próxima vez. ¿Cómo puedo optimizar el código anterior?
El método de @ lorean funcionará (nota: ahora se borra la respuesta) , pero solo tendrías un único espacio de almacenamiento. Entonces, si quisiera usar esto en varias instancias y hacer que cada instancia calcule un valor distinto, no funcionaría.
Afortunadamente, el tiempo de ejecución de Objective-C tiene esta cosa llamada Objetos Asociados que puede hacer exactamente lo que usted quiere:
#import <objc/runtime.h>
static void *MyClassResultKey;
@implementation MyClass
- (NSString *)test {
NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
if (result == nil) {
// do a lot of stuff
result = ...;
objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return result;
}
@end
La respuesta dada funciona muy bien y mi propuesta es solo una extensión que evita escribir demasiado código repetitivo.
Para evitar escribir repetidamente métodos getter y setter para propiedades de categoría, esta respuesta introduce macros. Además, estas macros facilitan el uso de propiedades de tipo primitivo como int
o BOOL
.
Enfoque tradicional sin macros
Tradicionalmente define una propiedad de categoría como
@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end
Luego debe implementar un método getter y setter utilizando un objeto asociado y el selector get como la clave ( ver respuesta original ):
#import <objc/runtime.h>
@implementation MyClass (Category)
- (NSString *)text{
return objc_getAssociatedObject(self, @selector(text));
}
- (void)setText:(NSString *)text{
objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
Mi enfoque sugerido
Ahora, usando una macro escribirás en su lugar:
@implementation MyClass (Category)
CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)
@end
Las macros se definen de la siguiente manera:
#import <objc/runtime.h>
#define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
#define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)
#define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
#define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
#define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
#define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)
La macro CATEGORY_PROPERTY_GET_SET
agrega un getter y setter para la propiedad dada. Las propiedades de solo lectura o solo escritura usarán las macros CATEGORY_PROPERTY_GET
y CATEGORY_PROPERTY_SET
respectivamente.
Los tipos primitivos necesitan un poco más de atención
Como los tipos primitivos no son objetos, las macros anteriores contienen un ejemplo para usar unsigned int
como el tipo de propiedad. Lo hace envolviendo el valor entero en un objeto NSNumber
. Entonces su uso es análogo al ejemplo anterior:
@interface ...
@property unsigned int value;
@end
@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end
Siguiendo este patrón, simplemente puede agregar más macros para admitir signed int
, BOOL
, etc ...
Limitaciones
Todas las macros están usando
OBJC_ASSOCIATION_RETAIN_NONATOMIC
de forma predeterminada.Los IDEs como el Código de la aplicación actualmente no reconocen el nombre del colocador al refacturar el nombre de la propiedad. Necesitarías cambiar el nombre por ti mismo.
Otra solución posible, quizás más fácil, que no utiliza Associated Objects
es declarar una variable en el archivo de implementación de la categoría de la siguiente manera:
@interface UIAlertView (UIAlertViewAdditions)
- (void)setObject:(id)anObject;
- (id)object;
@end
@implementation UIAlertView (UIAlertViewAdditions)
id _object = nil;
- (id)object
{
return _object;
}
- (void)setObject:(id)anObject
{
_object = anObject;
}
@end
La desventaja de este tipo de implementación es que el objeto no funciona como una variable de instancia, sino como una variable de clase. Además, los atributos de propiedad no se pueden asignar (como los utilizados en Objetos asociados como OBJC_ASSOCIATION_RETAIN_NONATOMIC)
Probado solo con iOS 9 Ejemplo: Agregar una propiedad UIView a UINavigationBar (Categoría)
UINavigationBar + Helper.h
#import <UIKit/UIKit.h>
@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end
UINavigationBar + Helper.m
#import "UINavigationBar+Helper.h"
#import <objc/runtime.h>
#define kTKLogoViewKey @"tkLogoView"
@implementation UINavigationBar (Helper)
- (void)setTkLogoView:(UIView *)tkLogoView {
objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)tkLogoView {
return objc_getAssociatedObject(self, kTKLogoViewKey);
}
@end
Solo use la biblioteca libextobjc :
archivo h:
@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end
m-file:
#import <extobjc.h>
@implementation MyClass (Variant)
@synthesizeAssociation (MyClass, test);
@end
.h-file
@interface NSObject (LaserUnicorn)
@property (nonatomic, strong) LaserUnicorn *laserUnicorn;
@end
.m-file
#import <objc/runtime.h>
static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;
@implementation NSObject (LaserUnicorn)
- (LaserUnicorn *)laserUnicorn {
return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
}
- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
Al igual que una propiedad normal, accesible con notación de puntos
NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);
Sintaxis más fácil
Alternativamente, puede usar @selector(nameOfGetter)
lugar de crear una clave de puntero estático como esta:
- (LaserUnicorn *)laserUnicorn {
return objc_getAssociatedObject(self, @selector(laserUnicorn));
}
- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Para obtener más información, consulte https://.com/a/16020927/202451