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


122

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 IVar _test na primeira vez que o método é chamado, e apenas retornando esse IVar na próxima vez. Como posso otimizar o código acima?


2
Por que não fazer o que você normalmente faz, apenas em vez de uma categoria, adicionar a propriedade a uma classe base MyClass? E, para levar adiante, execute suas tarefas pesadas em segundo plano e faça com que o processo inicie uma notificação ou chame algum manipulador para MyClass quando o processo estiver concluído.
Jeremy

4
MyClass é uma classe gerada a partir dos dados principais. Se eu mas meu código de objeto personalizado dentro da classe gerada, ele desapareceria se eu regenerar a classe dos meus Dados Principais. Por causa disso, estou usando uma categoria.
DHRM

1
Talvez aceite a pergunta que melhor se aplica ao título? ("Propriedade na categoria")
hfossli

Por que não apenas criar uma subclasse?
Scott Zhu

Respostas:


124

O método do @ coreano funcionará (nota: a resposta agora foi excluída) , mas você só teria um único slot de armazenamento. Portanto, se você quiser usá-lo 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 uma coisa chamada Objetos Associados que pode fazer exatamente o que você deseja:

#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


5
@DaveDeLong Obrigado por esta solução! Não é muito bonito, mas funciona :)
DHRM

42
"não é muito bonito"? Essa é a beleza do Objective-C! ;)
Dave DeLong

6
Ótima resposta! Você pode até conseguir livrar a variável estática usando @selector(test)como uma chave, como explicado aqui: stackoverflow.com/questions/16020918/...
Gabriele Petronella

1
@HotFudgeSunday - acho que funciona rapidamente: stackoverflow.com/questions/24133058/…
Scott Corscadden #

174

arquivo .h

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

arquivo .m

#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

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ê pode usar em @selector(nameOfGetter)vez de criar uma chave de ponteiro estática da seguinte maneira:

- (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


4
Bom artigo. Uma coisa a observar é uma atualização no artigo. Atualização 22 de dezembro de 2011: É importante observar que a chave da associação é uma tecla void poid void *, não uma string. Isso significa que, ao recuperar uma referência associada, você deve passar exatamente o mesmo ponteiro para o tempo de execução. Não funcionaria como planejado se você usasse uma cadeia C como chave, depois a copiasse para outro local na memória e tentasse acessar a referência associada passando o ponteiro para a cadeia copiada como chave.
Rogers,

4
Você realmente não precisa do @dynamic objectTag;. @dynamicsignifica que o setter & getter serão gerados em outro lugar, mas, neste caso, eles serão implementados aqui.
IluTov

@NSAddict true! Fixo!
Hfossli

1
E o negócio de laserUnicorns para gerenciamento manual de memória? Isso é um vazamento de memória?
Manny

É lançado automaticamente stackoverflow.com/questions/6039309/…
hfossli

32

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

Para evitar escrever métodos getter e setter repetidamente para propriedades de categoria, esta resposta apresenta macros. Além disso, essas macros facilitam o uso de propriedades do tipo primitivo, como intou BOOL.

Abordagem tradicional sem macros

Tradicionalmente, você define uma propriedade de categoria como

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

Em seguida, você precisa implementar um método getter e setter usando um objeto associado e o seletor get como a chave ( consulte a resposta 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

Minha abordagem sugerida

Agora, usando uma macro, você escreverá:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

As macros são definidas da seguinte maneira:

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

A macro CATEGORY_PROPERTY_GET_SETadiciona um getter e setter para a propriedade especificada. As propriedades somente leitura ou somente gravação usarão a macro CATEGORY_PROPERTY_GETe, CATEGORY_PROPERTY_SETrespectivamente.

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

Como os tipos primitivos não são objetos, as macros acima contêm um exemplo para uso unsigned intcomo o tipo da propriedade. Isso é feito envolvendo o valor inteiro em um NSNumberobjeto. 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 apoiar signed int, BOOL, etc ...

Limitações

  1. Todas as macros estão sendo usadas OBJC_ASSOCIATION_RETAIN_NONATOMICpor padrão.

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


1
não se esqueça de #import <objc/runtime.h>na categoria .m arquivo caso contrário. erro em tempo de compilação: A declaração implícita da função 'objc_getAssociatedObject' é inválida no C99 . stackoverflow.com/questions/9408934/…
Sathe_Nagaraja 25/01

7

Basta usar a biblioteca libextobjc :

arquivo h:

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

arquivo m:

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

@synthesizeAssociation (MyClass, test);

@end

Mais sobre @synthesizeAssociation


O que é a maneira correta de substituir um acessador com isso (por exemplo carga preguiçoso uma propriedade)
David James

3

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

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

-2

Outra solução possível, talvez mais fácil, que não usa Associated Objectsé declarar uma variável no arquivo de implementação da 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 objeto não funciona como uma variável de instância, mas como uma variável de classe. Além disso, não é possível atribuir atributos de propriedade (como usado em objetos associados, como OBJC_ASSOCIATION_RETAIN_NONATOMIC)


A pergunta pergunta sobre a variável de instância. Sua solução é como Lorean
hfossli

1
Realmente?? Você sabia o que está fazendo? Você criou um par de método getter / setter para acessar uma variável interna que é independente para um arquivo, MAS um objeto de classe, ou seja, se você alocar dois objetos de classe UIAlertView, os valores dos objetos são os mesmos!
Itachi
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.