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?
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
. 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
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
Toutes les macros utilisent OBJC_ASSOCIATION_RETAIN_NONATOMIC
par défaut.
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.
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
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