Objective-C: змінна властивість / екземпляр у категорії


122

Оскільки я не можу створити синтезовану властивість у категорії в 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 наступного разу. Як я можу оптимізувати наведений вище код?


2
Чому б не зробити те, що ви зазвичай робите, лише замість категорії додайте властивість до базового класу MyClass? І щоб продовжити це, виконайте ваші важкі речі на задньому плані і відключіть повідомлення, або зателефонуйте до обробника для MyClass, коли процес завершиться.
Джеремі

4
MyClass - це генерований клас із основних даних. Якщо я, але мій власний об'єктний код всередині генерованого класу, він би зникав, якщо я регенерував клас із моїх основних даних. Через це я використовую категорію.
dhrm

1
Може, прийняти питання, яке найкраще стосується назви? ("Власність у категорії")
hfossli

Чому б просто не створити підклас?
Скотт Чжу

Відповіді:


124

Метод @ 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

виправлена ​​посилання на асоційовані об’єкти: developer.apple.com/library/ios/#documentation/cocoa/Conceptual/…
MechEthan

5
@DaveDeLong Дякую за таке рішення! Не дуже красиво, але це працює :)
dhrm

42
"не дуже красива"? Це краса Objective-C! ;)
Дейв ДеЛонг

6
Чудова відповідь! Ви навіть можете позбутися статичної змінної, використовуючи @selector(test)як ключ, як пояснено тут: stackoverflow.com/questions/16020918/…
Габріеле Петронелла

1
@HotFudgeSunday - думаю , що це робить роботу в стрижа: stackoverflow.com/questions/24133058 / ...
Скотт Corscadden

174

.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


4
Гарна стаття. Варто відзначити одне оновлення в статті. Оновлення 22 грудня 2011 р. Важливо зауважити, що ключем для асоціації є недійсний вказівник * ключ void *, а не рядок. Це означає, що при отриманні пов’язаної посилання вам потрібно передати такий самий покажчик на час виконання. Це не працюватиме за призначенням, якби ви використовували рядок C в якості ключа, потім скопіювали рядок в інше місце в пам'яті і намагалися отримати доступ до пов'язаної посилання, передавши вказівник на скопійований рядок як ключ.
Містер Роджерс

4
Вам справді не потрібні @dynamic objectTag;. @dynamicозначає, що сеттер і геттер будуть генеровані десь ще, але в цьому випадку вони реалізовані саме тут.
IluTov

@NSAddict правда! Виправлено!
hfossli

1
Як щодо угоди лазерних універсалів для ручного управління пам'яттю? Це витік пам'яті?
Менні

Випущена автоматично stackoverflow.com/questions/6039309 / ...
hfossli

32

Дана відповідь чудово працює, і моя пропозиція - лише розширення до неї, що дозволяє уникнути написання занадто великого кодового коду.

Щоб уникнути написання багаторазових методів getter та setter для властивостей категорії, ця відповідь вводить макроси. Крім того, ці макроси полегшують використання властивостей примітивного типу, таких як intабо BOOL.

Традиційний підхід без макросів

Традиційно ви визначаєте категорію властивості типу

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

Тоді вам потрібно реалізувати метод getter і setter, використовуючи асоційований об'єкт і селектор get як ключ ( див. Оригінальну відповідь ):

#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додає getter та setter для даної властивості. Властивості лише для читання або для запису будуть використовувати відповідно 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, такі як Код додатка , в даний час не розпізнають ім'я налаштування під час рефакторингу імені ресурсу. Вам потрібно було б перейменувати його самостійно.


1
не забудьте #import <objc/runtime.h>у файлі категорії .m інакше. помилка часу компіляції: Неприпустиме оголошення функції "objc_getAssociatedObject" недійсне в C99 з'являється. stackoverflow.com/questions/9408934 / ...
Sathe_Nagaraja

7

Просто використовуйте бібліотеку libextobjc :

h-файл:

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

m-файл:

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

@synthesizeAssociation (MyClass, test);

@end

Більше про @synthesizeAssociation


Який правильний спосіб перемогти з цим аксесуаром (наприклад, ледачий завантаження об’єкта)
Девід Джеймс,

3

Тестується лише за допомогою iOS 9 Приклад: Додавання властивості UIView до UINavigationBar (категорія)

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

-2

Іншим можливим рішенням, можливо, простішим, яке не використовується, 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)


Питання задається про змінну екземпляра. Ваше рішення так само, як
Лорен

1
Дійсно ?? Ти знав, що ти робиш? Ви створили пару методу getter / setter для доступу до внутрішньої змінної, незалежної для файлу, АЛЕ об’єкт класу, тобто якщо ви виділите два об’єкти класу UIAlertView, значення їх об'єктів однакові!
Ітачі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.