web-dev-qa-db-fra.com

Objective-C: Variable propriété / 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 méthode de test est appelé plusieurs fois au moment 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 lors du premier appel de la méthode et retourne simplement cet IVar la prochaine fois. Comment puis-je optimiser le code ci-dessus?

117
dhrm

La méthode de @ Lorean fonctionnera (note: la réponse est maintenant supprimée), mais vous n’auriez qu’un seul emplacement de stockage. Donc, si vous souhaitez utiliser cela sur plusieurs instances et laisser chaque instance calculer une valeur distincte, cela ne fonctionnera pas.

Heureusement, le runtime d'Objective-C a cette chose appelée 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
122
Dave DeLong

. 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

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

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

Syntaxe plus simple

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

169
hfossli

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

Afin d'éviter d'écrire à plusieurs reprises des 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 de propriétés de type primitif telles que int ou 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 and setter en utilisant un objet associé et le sélecteur en tant que clé ( voir la réponse initiale ):

#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

Mon approche suggérée

Maintenant, en utilisant une macro, vous écrirez à 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 utilisent le CATEGORY_PROPERTY_GET et 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 unsigned int comme type de la propriété. Pour cela, il encapsule la valeur entière dans un objet NSNumber. Son utilisation est donc analogue à 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 plusieurs macros pour prendre également en charge signed int, BOOL, etc ...

Limitations

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

  2. Les IDE tels que Code App ne reconnaissent actuellement pas le nom du créateur lors de la refactorisation du nom de la propriété. Vous devrez le renommer vous-même.

31
Lars Blumberg

Il suffit d'utiliser la bibliothèque libextobjc :

h-file:

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

m-file:

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

@synthesizeAssociation (MyClass, test);

@end

Plus d'informations sur @synthesizeAssociation

7
Mansurov Ruslan

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
3
Rikco