Переменные экземпляра для категорий Objective C - PullRequest
32 голосов
/ 10 ноября 2010

У меня есть ситуация, когда мне кажется, что мне нужно добавить переменные экземпляра в категорию, но я знаю из документов Apple, что я не могу этого сделать. Поэтому мне интересно, какова лучшая альтернатива или обходной путь.

Что я хочу сделать, это добавить категорию, которая добавляет функциональность в UIViewControllers. Я нашел бы это полезным во всех моих различных UIViewController, независимо от того, какой конкретный подкласс UIViewController они расширяют, поэтому я считаю, что категория - лучшее решение. Чтобы реализовать эту функцию, мне нужно несколько различных методов, и мне нужно отслеживать данные между ними, поэтому я и захотел создавать методы экземпляра.

В случае, если это полезно, вот что я конкретно хочу сделать. Я хочу, чтобы было легче отслеживать, когда программная клавиатура скрывается и отображается, чтобы я мог изменять размер содержимого в моем представлении. Я обнаружил, что единственный надежный способ сделать это - поместить код в четыре разных метода UIViewController и отслеживать дополнительные данные в переменных экземпляра. Поэтому эти методы и переменные экземпляра - это то, что я хотел бы поместить в категорию, поэтому мне не нужно копировать и вставлять их каждый раз, когда мне нужно обращаться с программной клавиатурой. (Если есть более простое решение для этой точной проблемы, это тоже хорошо - но я все же хотел бы знать ответ на переменные экземпляра категории для дальнейшего использования!)

Ответы [ 8 ]

28 голосов
/ 10 ноября 2010

Да, вы можете сделать это, но раз вы спрашиваете, я должен спросить: вы абсолютно уверены, что вам нужно? (Если вы скажете «да», вернитесь назад, выясните, что вы хотите сделать, и посмотрите, есть ли другой способ сделать это)

Однако, если вы действительно хотите внедрить хранилище в класс, которым вы не управляете, используйте ассоциативную ссылку .

14 голосов
/ 03 апреля 2012

Недавно мне нужно было это сделать (добавить состояние в категорию). @Dave DeLong имеет правильную точку зрения на это. Изучая лучший подход, я нашел отличный пост в блоге Тома Харрингтона. Мне нравится идея @ JeremyP об использовании объявлений @property в категории, но не его конкретная реализация (не фанат глобального синглтона или глобальные ссылки). Ассоциативные ссылки - путь.

Вот код для добавления (как представляется) иваров в вашу категорию. Я подробно писал об этом здесь .

В File.h вызывающая сторона видит только чистую высокоуровневую абстракцию:

@interface UIViewController (MyCategory)
@property (retain,nonatomic) NSUInteger someObject;
@end

В File.m мы можем реализовать @property (ПРИМЕЧАНИЕ: это не может быть @ synthesize'd):

@implementation UIViewController (MyCategory)

- (NSUInteger)someObject
{
  return [MyCategoryIVars fetch:self].someObject;
}

- (void)setSomeObject:(NSUInteger)obj
{
  [MyCategoryIVars fetch:self].someObject = obj;
}

Нам также нужно объявить и определить класс MyCategoryIVars. Для простоты понимания я объяснил это из правильного порядка компиляции. @Interface необходимо поместить перед реализацией Category @.

@interface MyCategoryIVars : NSObject
@property (retain,nonatomic) NSUInteger someObject;
+ (MyCategoryIVars*)fetch:(id)targetInstance;
@end

@implementation MyCategoryIVars

@synthesize someObject;

+ (MyCategoryIVars*)fetch:(id)targetInstance
{
  static void *compactFetchIVarKey = &compactFetchIVarKey;
  MyCategoryIVars *ivars = objc_getAssociatedObject(targetInstance, &compactFetchIVarKey);
  if (ivars == nil) {
    ivars = [[MyCategoryIVars alloc] init];
    objc_setAssociatedObject(targetInstance, &compactFetchIVarKey, ivars, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [ivars release];
  } 
  return ivars;
}

- (id)init
{
  self = [super init];
  return self;
}

- (void)dealloc
{
  self.someObject = nil;
  [super dealloc];
}

@end

Приведенный выше код объявляет и реализует класс, который содержит наши ivars (someObject). Поскольку мы не можем реально расширить UIViewController, это нужно сделать.

6 голосов
/ 17 июля 2015

Лучше всего это достигается с помощью встроенной функции ObjC Associated Objects (также называемой Associated References), в приведенном ниже примере просто измените свою категорию и замените relatedObject на имя вашей переменной.

NSObject + AssociatedObject.h

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject + AssociatedObject.m

#import <objc/runtime.h>

@implementation NSObject (AssociatedObject)
@dynamic associatedObject;

- (void)setAssociatedObject:(id)object {
     objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)associatedObject {
    return objc_getAssociatedObject(self, @selector(associatedObject));
}

Смотрите здесь полный урок:

http://nshipster.com/associated-objects/

6 голосов
/ 10 ноября 2010

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

Более хакерское решение:

Создайте одноэлементный NSDictionary, который будет иметь UIViewController в качестве ключа (или, скорее, его адрес, обернутый как NSValue) и значение вашего свойства в качестве его значения.

Создание метода получения и установки свойства, которое фактически идет в словарь для получения / установки свойства.

@interface UIViewController(MyProperty)

@property (nonatomic, retain) id myProperty;
@property (nonatomic, readonly, retain) NSMutableDcitionary* propertyDictionary;

@end

@implementation  UIViewController(MyProperty)

-(NSMutableDictionary*) propertyDictionary
{
    static NSMutableDictionary* theDictionary = nil;
    if (theDictionary == nil)
    {
        theDictioanry = [[NSMutableDictionary alloc] init];
    }
    return theDictionary;
}


-(id) myProperty
{
    NSValue* key = [NSValue valueWithPointer: self];
    return [[self propertyDictionary] objectForKey: key];
}

-(void) setMyProperty: (id) newValue
{
    NSValue* key = [NSValue valueWithPointer: self];
    [[self propertyDictionary] setObject: newValue forKey: key];    
}

@end

Две потенциальные проблемы с вышеуказанным подходом:

  • нет способа удалить ключи контроллеров представления, которые были освобождены. Пока вы отслеживаете только несколько, это не должно быть проблемой. Или вы можете добавить метод удаления ключа из словаря, как только узнаете, что с ним покончено.
  • Я не уверен на 100%, что isEqual: метод NSValue сравнивает содержимое (то есть, обернутый указатель), чтобы определить равенство или просто сравнивает себя, чтобы увидеть, является ли объект сравнения точно таким же NSValue. В последнем случае вам придется использовать NSNumber вместо NSValue для ключей (NSNumber numberWithUnsignedLong: поможет на 32-битной и 64-битной платформах).
3 голосов
/ 12 октября 2015

Во многих документах онлайн упоминалось, что вы не можете создать новую переменную в категории, но я нашел очень простой способ добиться этого. Вот способ, который позволяет объявлять новую переменную в категории.

В вашем .h файле

@interface UIButton (Default)

   @property(nonatomic) UIColor *borderColor;

@end  

В вашем .m файле

#import <objc/runtime.h>
static char borderColorKey;

@implementation UIButton (Default)

- (UIColor *)borderColor
{
    return objc_getAssociatedObject(self, &borderColorKey);
}

- (void)setBorderColor:(UIColor *)borderColor
{
    objc_setAssociatedObject(self, &borderColorKey,
                             borderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    self.layer.borderColor=borderColor.CGColor;
}

@end

Теперь у вас есть новая переменная.

0 голосов
/ 10 ноября 2010

Я считаю, что можно добавить переменные в класс, используя среду выполнения Obj-C.
Я нашел это обсуждение также.

0 голосов
/ 10 ноября 2010

В зависимости от того, что вы делаете, вы можете использовать методы статической категории.

Итак, я полагаю, у вас есть такая проблема:

ScrollView содержит несколько текстовых сообщений. Пользователь вводит текст при редактировании, вы хотите прокрутить представление прокрутки, чтобы редактирование текста было видно над клавиатурой.

+ (void) staticScrollView: (ScrollView*)sv scrollsTo:(id)someView
{
  // scroll view to someviews's position or some such.
}

возврат из этого не обязательно потребует, чтобы представление вернулось назад, и поэтому ему не нужно ничего хранить.

Но это все, что я могу думать без примеров кода, извините.

0 голосов
/ 10 ноября 2010

Почему бы просто не создать подкласс UIViewController, добавить к нему функциональность, а затем использовать этот класс (или его подкласс) вместо этого?

...