Objective-C: переменная свойства / экземпляра в категории
Как я не могу создать синтезированное свойство в категории в Objective-C, я не знаю как оптимизировать следующий код:
@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
на
6 ответов
метод @ lorean будет работать (Примечание: ответ удален), но у вас будет только один слот для хранения. Поэтому, если вы хотите использовать это на нескольких экземплярах и каждый экземпляр вычисляет отдельное значение, это не сработает.
к счастью, среда выполнения Objective-C имеет эту вещь, называемую Связанные Объекты это может сделать именно то, что вы хотите:
#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-файле
@interface NSObject (LaserUnicorn)
@property (nonatomic, strong) LaserUnicorn *laserUnicorn;
@end
.м-файл
#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
так же, как обычное свойство - доступно с точечной нотацией
NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);
более простой синтаксис
в качестве альтернативы вы можете использовать @selector(nameOfGetter)
вместо создания статического ключа указателя, например:
- (LaserUnicorn *)laserUnicorn {
return objc_getAssociatedObject(self, @selector(laserUnicorn));
}
- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
дополнительные сведения см. В разделе https://stackoverflow.com/a/16020927/202451
данный ответ Отлично работает, и мое предложение - это просто расширение, которое позволяет избежать написания слишком много шаблонного кода.
чтобы избежать многократного написания методов getter и setter для свойств категории, этот ответ вводит макросы. Кроме того, эти макросы облегчают использование свойств примитивного типа, таких как int
или BOOL
.
традиционный подход без макросов
традиционно вы определяете категорию собственность как
@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end
тогда вам нужно реализовать метод геттера и сеттера с помощью Связанный объект и сделать селектор как ключ (посмотреть исходный ответ):
#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
мой предложенный подход
теперь, используя макрос, вы напишете вместо этого:
@implementation MyClass (Category)
CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)
@end
макросы определяются следующим образом:
#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)
макрос CATEGORY_PROPERTY_GET_SET
добавляет геттер и сеттер для данного свойства. Свойства только для чтения или записи будут использовать CATEGORY_PROPERTY_GET
и CATEGORY_PROPERTY_SET
макрос соответственно.
примитивные типы требуют немного больше внимания
поскольку примитивные типы не являются объектами, приведенные выше макросы содержат пример использования unsigned int
как тип свойства. Он делает это, обертывая целочисленное значение в
просто использовать libextobjc библиотека:
h-файл:
@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end
M-файл:
#import <extobjc.h>
@implementation MyClass (Variant)
@synthesizeAssociation (MyClass, test);
@end
протестировано только с iOS 9 Пример: добавление свойства UIView в UINavigationBar (Category)
UINavigationBar+Помощник.h
#import <UIKit/UIKit.h>
@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end
UINavigationBar+Помощник.м
#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
другое возможное решение, возможно, проще, которое не использует Associated Objects
объявить переменную в файле реализации категории следующим образом:
@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
недостатком такого рода реализации является то, что объект функционирует не как переменная экземпляра, а скорее как переменная класса. Кроме того, атрибуты свойств не могут быть назначены (например, используемые в связанных объектах, таких как OBJC_ASSOCIATION_RETAIN_NONATOMIC)