Objective-C: variável de propriedade / instância na categoria

Como não consigo criar uma propriedade sintetizada em uma categoria no Objective-C, não sei como otimizar o seguinte 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 

O método de teste é chamado várias vezes em tempo de execução e estou fazendo muitas coisas para calcular o resultado. Normalmente, usando uma propriedade sintetizada, eu armazeno o valor em um _test IVar na primeira vez que o método é chamado, e apenas retornando este IVAR na próxima vez. Como posso otimizar o código acima?

O método do @ lorean funcionará (nota: a resposta foi excluída) , mas você só terá um único slot de armazenamento. Portanto, se você quiser usar isso em várias instâncias e fazer com que cada instância calcule um valor distinto, não funcionará.

Felizmente, o tempo de execução do Objective-C tem essa coisa chamada Objetos Associados que pode fazer exatamente o que você está querendo:

 #import  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 

.h-file

 @interface NSObject (LaserUnicorn) @property (nonatomic, strong) LaserUnicorn *laserUnicorn; @end 

.m-file

 #import  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 

Assim como uma propriedade normal – acessível com notação de ponto

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

Sintaxe mais fácil

Como alternativa, você poderia usar @selector(nameOfGetter) vez de criar uma chave de ponteiro estática da seguinte forma:

 - (LaserUnicorn *)laserUnicorn { return objc_getAssociatedObject(self, @selector(laserUnicorn)); } - (void)setLaserUnicorn:(LaserUnicorn *)unicorn { objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } 

Para mais detalhes, consulte https://stackoverflow.com/a/16020927/202451

A resposta dada funciona muito bem e minha proposta é apenas uma extensão para ela, que evita escrever muito código clichê.

Para evitar a criação de methods repetitivos getter e setter para propriedades de categoria, essa resposta apresenta macros. Além disso, essas macros facilitam o uso de propriedades de tipo primitivo, como int ou BOOL .

Abordagem tradicional sem macros

Tradicionalmente, você define uma propriedade de categoria como

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

Então você precisa implementar um método getter e setter usando um object associado e o get selector como a chave ( veja a resposta original ):

 #import  @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 

Minha abordagem sugerida

Agora, usando uma macro, você irá escrever:

 @implementation MyClass (Category) CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:) @end 

As macros são definidas da seguinte forma:

 #import  #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) 

A macro CATEGORY_PROPERTY_GET_SET adiciona um getter e setter para a propriedade especificada. As propriedades somente leitura ou somente gravação usarão as macros CATEGORY_PROPERTY_SET e CATEGORY_PROPERTY_SET respectivamente.

Tipos primitivos precisam de um pouco mais de atenção

Como os tipos primitivos não são objects, as macros acima contêm um exemplo para usar unsigned int como o tipo da propriedade. Ele faz isso envolvendo o valor inteiro em um object NSNumber . Portanto, seu uso é análogo ao exemplo anterior:

 @interface ... @property unsigned int value; @end @implementation ... CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:) @end 

Seguindo esse padrão, você pode simplesmente adicionar mais macros para também suportar signed int , BOOL , etc …

Limitações

  1. Todas as macros estão usando OBJC_ASSOCIATION_RETAIN_NONATOMIC por padrão.

  2. IDEs como App Code atualmente não reconhecem o nome do setter ao refatorar o nome da propriedade. Você precisaria renomeá-lo sozinho.

Apenas use a biblioteca libextobjc :

arquivo h:

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

m-file:

 #import  @implementation MyClass (Variant) @synthesizeAssociation (MyClass, test); @end 

Mais sobre @synthesizeAssociation

Testado apenas com o iOS 9 Exemplo: Adicionando uma propriedade UIView ao UINavigationBar (Categoria)

UINavigationBar + Helper.h

 #import  @interface UINavigationBar (Helper) @property (nonatomic, strong) UIView *tkLogoView; @end 

UINavigationBar + Helper.m

 #import "UINavigationBar+Helper.h" #import  #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 
 //  @interface MyObj : NSObject { @private NSMutableArray *_arr; } @end //  @interface MyObj(MyCategory) @property (nonatomic, strong) NSMutableArray *array; @end //  #import "MyObj.h" @implementation MyObj (MyCategory) - (void)setArray:(NSMutableArray *)array { _arr = array; } - (NSMutableArray*)array { if (!_arr) _arr = [NSMutableArray arrayWithObject:@"LOL"]; return _arr; } @end //  #import "MyObj+MyCategory.h" @implementation MyObj // // test description // NSLog(@"%@", [MyObj new]); // - (NSString*)description { return self.array.description; } @end 

Outra solução possível, talvez mais fácil, que não usa Associated Objects é declarar uma variável no arquivo de implementação de categoria da seguinte maneira:

 @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 

A desvantagem desse tipo de implementação é que o object não funciona como uma variável de instância, mas como uma variável de class. Além disso, os atributos de propriedade não podem ser atribuídos (como usado em objects associados como OBJC_ASSOCIATION_RETAIN_NONATOMIC)