Objective-C: Variable de propiedad / instancia en categoría


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 tiempo de ejecución y estoy haciendo muchas cosas para calcular el resultado. Normalmente, usando una propiedad sintetizada, almaceno el valor en un IVAR _test la primera vez que se llama al método, y simplemente devuelvo este IVar la próxima vez. ¿Cómo puedo optimizar el código anterior?

Author: hfossli, 2012-01-04

6 answers

El método de @ lorean funcionará (nota: la respuesta se ha eliminado), pero solo tendrías una ranura de almacenamiento. Así que si desea utilizar esto en varias instancias y tienen cada instancia calcular 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 desea:

#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
 117
Author: Dave DeLong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-28 07:30:40

.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 dot-notation

NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);

Sintaxis más fácil

Alternativamente, puedes usar @selector(nameOfGetter) en lugar de crear una clave de puntero estática 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 más detalles ver https://stackoverflow.com/a/16020927/202451

 156
Author: hfossli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:44

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 se define una propiedad de categoría como

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

Entonces debe implementar un método getter y setter usando un objeto asociado y el selector get como 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 añade un getter y un setter para la propiedad dada. Las propiedades de solo lectura o solo escritura usarán las macro 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 tipo de propiedad. Lo hace envolviendo el valor entero en un objeto NSNumber. Así que 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 que también sean compatibles signed int, BOOL, etc...

Limitaciones

  1. Todos las macros usan OBJC_ASSOCIATION_RETAIN_NONATOMIC de forma predeterminada.

  2. IDEs como App Code actualmente no reconocen el nombre del configurador al refactorizar el nombre de la propiedad. Tendrías que cambiarle el nombre por ti mismo.

 28
Author: Lars Blumberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-25 19:24:46

Simplemente use libextobjc biblioteca:

H-file:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

M-file:

#import <extobjc.h>
@implementation MyClass (Variant)

@synthesizeAssociation (MyClass, test);

@end

Más sobre @synthesizeAssociation

 7
Author: Mansurov Ruslan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-05 14:57:09

Probado solo con iOS 9 Ejemplo: Agregar una propiedad UIView a UINavigationBar (Category)

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
 3
Author: Rikco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-05 13:39:48

Otra solución posible, quizás más fácil, que no usa Associated Objects es declarar una variable en el archivo de implementación de 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 se usa en Objetos Asociados como OBJC_ASSOCIATION_RETAIN_NONATOMIC)

 -2
Author: kernix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-25 12:04:02