125 votes

Objective-C : Propriété / variable d'instance dans la catégorie

Comme je ne peux pas créer une propriété synthétisée dans une catégorie en Objective-C, je ne sais pas comment optimiser le code suivant :

@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

Le site méthode d'essai est appelé plusieurs fois lors de l'exécution et je fais beaucoup de choses pour calculer le résultat. Normalement, en utilisant une propriété synthétisée, je stocke la valeur dans un IVar _test la première fois que la méthode est appelée, et je retourne cet IVar la fois suivante. Comment puis-je optimiser le code ci-dessus ?

2 votes

Pourquoi ne pas faire ce que vous faites normalement, mais au lieu d'une catégorie, ajouter la propriété à une classe de base MyClass ? Et pour aller plus loin, effectuez vos tâches lourdes en arrière-plan et faites en sorte que le processus déclenche une notification ou appelle un gestionnaire pour MyClass lorsque le processus est terminé.

4 votes

MyClass est une classe générée à partir de Core Data. Si je mets mon code objet personnalisé dans la classe générée, il disparaîtra si je régénère la classe à partir de mes données de base. Pour cette raison, j'utilise une catégorie.

1 votes

Acceptez peut-être la question qui correspond le mieux au titre ("Propriété dans la catégorie").

177voto

hfossli Points 6815

Fichier .h

@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

Tout comme une propriété normale - accessible avec la notation par points

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

Une syntaxe plus facile

Vous pouvez également utiliser @selector(nameOfGetter) au lieu de créer une clé de pointeur statique comme ceci :

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, @selector(laserUnicorn));
}

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

Pour plus de détails, voir https://stackoverflow.com/a/16020927/202451

4 votes

Bon article. Une chose à noter est une mise à jour dans l'article. Mise à jour du 22 décembre 2011 : Il est important de noter que la clé de l'association est un pointeur void *key, et non une chaîne de caractères. Cela signifie que lorsque vous récupérez une référence associée, vous devez passer exactement le même pointeur au runtime. Cela ne fonctionnerait pas comme prévu si vous utilisiez une chaîne C comme clé, puis copiez la chaîne à un autre endroit de la mémoire et essayez d'accéder à la référence associée en passant le pointeur à la chaîne copiée comme clé.

4 votes

Vous n'avez vraiment pas besoin de la @dynamic objectTag; . @dynamic signifie que le setter et le getter seront générés ailleurs, mais dans ce cas, ils sont implémentés ici même.

0 votes

@NSAddict vrai ! Fixe !

126voto

Dave DeLong Points 156978

La méthode d'@lorean fonctionnera (note : la réponse est maintenant supprimée) mais vous n'auriez qu'un seul emplacement de stockage. Donc, si vous vouliez l'utiliser sur plusieurs instances et faire en sorte que chaque instance calcule une valeur distincte, cela ne fonctionnerait pas.

Heureusement, le runtime Objective-C dispose d'un dispositif appelé Objets associés qui peut faire exactement ce que vous voulez :

#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

0 votes

Correction du lien pour les objets associés : developer.apple.com/library/ios/#documentation/cocoa/Conceptual/

5 votes

@DaveDeLong Merci pour cette solution ! Pas très belle mais ça marche :)

42 votes

"pas très beau" ? C'est la beauté de l'Objective-C ! ;)

33voto

Lars Blumberg Points 1530

La réponse donnée fonctionne très bien et ma proposition n'est qu'une extension de celle-ci qui évite d'écrire trop de code passe-partout.

Afin d'éviter l'écriture répétée de méthodes getter et setter pour les propriétés de catégorie, cette réponse introduit des macros. De plus, ces macros facilitent l'utilisation des propriétés de type primitif telles que int o BOOL .

Approche traditionnelle sans macros

Traditionnellement, vous définissez une propriété de catégorie comme

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

Ensuite, vous devez implémenter une méthode getter et setter en utilisant un fichier de type objet associé y el obtenir un sélecteur comme clé ( voir la réponse originale ) :

#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

L'approche que je propose

Maintenant, en utilisant une macro, vous allez écrire à la place :

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

Les macros sont définies comme suit :

#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 ajoute un getter et un setter pour la propriété donnée. Les propriétés en lecture seule ou en écriture seule utiliseront la balise CATEGORY_PROPERTY_GET y CATEGORY_PROPERTY_SET macro respectivement.

Les types primitifs nécessitent un peu plus d'attention

Comme les types primitifs ne sont pas des objets, les macros ci-dessus contiennent un exemple d'utilisation de la fonction unsigned int comme type de propriété. Pour ce faire, la valeur entière est enveloppée dans un fichier de type NSNumber objet. Son utilisation est donc analogue à celle de l'exemple précédent :

@interface ...
@property unsigned int value;
@end

@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end

En suivant ce modèle, vous pouvez simplement ajouter d'autres macros pour prendre également en charge les éléments suivants signed int , BOOL etc...

Limites

  1. Toutes les macros utilisent OBJC_ASSOCIATION_RETAIN_NONATOMIC par défaut.

  2. Des IDE comme Code de l'application ne reconnaissent pas actuellement le nom du setter lors du remaniement du nom de la propriété. Vous devez le renommer vous-même.

1 votes

N'oubliez pas de #import <objc/runtime.h> dans la catégorie du fichier .m sinon, erreur de compilation : La déclaration implicite de la fonction 'objc_getAssociatedObject' n'est pas valide en C99. apparaît. stackoverflow.com/questions/9408934/

7voto

Master Points 116

Il suffit d'utiliser libextobjc bibliothèque :

h-file :

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

m-file :

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

@synthesizeAssociation (MyClass, test);

@end

En savoir plus sur @synthesizeAssociation

3voto

Enrico Szonn Points 51

Testé uniquement avec iOS 9 Exemple : Ajout d'une propriété UIView à UINavigationBar (Catégorie)

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X