objective-c category

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

  1. Todas las macros están usando OBJC_ASSOCIATION_RETAIN_NONATOMIC de forma predeterminada.

  2. 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

Más acerca de @synthesizeAssociation


.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