Objective-C: свойство / экземпляр переменной в категории - PullRequest
117 голосов
/ 04 января 2012

Поскольку я не могу создать синтезированное свойство в категории в 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

Тестовый метод вызывается несколько раз во время выполнения, и я делаю много вещей для вычисления результата. Обычно, используя синтезированное свойство, я сохраняю значение в IVar _test при первом вызове метода и просто возвращаю этот IVar в следующий раз. Как я могу оптимизировать приведенный выше код?

Ответы [ 6 ]

168 голосов
/ 15 февраля 2013

.h-файл

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m-файл

#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

122 голосов
/ 05 января 2012

@ метод Лориана будет работать (примечание: ответ теперь удален) , но у вас будет только один слот для хранения. Поэтому, если вы хотите использовать это в нескольких экземплярах и каждый экземпляр вычисляет отдельное значение, это не сработает.

К счастью, во время выполнения 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
31 голосов
/ 02 апреля 2014

Данный ответ прекрасно работает, и мое предложение является лишь его расширением, позволяющим избежать написания слишком большого количества стандартного кода.

Во избежание неоднократной записи методов получения и установки для свойств категории в этом ответе вводятся макросы.Кроме того, эти макросы упрощают использование свойств примитивного типа, таких как 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 в качестве типа свойства.Это достигается путем помещения целочисленного значения в объект NSNumber.Таким образом, его использование аналогично предыдущему примеру:

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

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

Следуя этой схеме, вы можете просто добавить больше макросов для поддержки signed int, BOOL и т. Д. *

Ограничения

  1. По умолчанию все макросы используют OBJC_ASSOCIATION_RETAIN_NONATOMIC.

  2. IDE, такие как Код приложения в настоящее время не распознает имя сеттера при рефакторинге имени свойства.Вам нужно будет переименовать его самостоятельно.

7 голосов
/ 05 января 2015

Просто используйте libextobjc библиотека:

ч-файл:

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

м-файл:

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

@synthesizeAssociation (MyClass, test);

@end

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

3 голосов
/ 05 апреля 2016

Протестировано только с iOS 9 Пример: добавление свойства UIView в UINavigationBar (Category)

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
0 голосов
/ 25 февраля 2014

Другое возможное решение, возможно, более простое, в котором не используется 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)

...