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

подробнее о @synthesizeAssociation


протестировано только с 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)