Подтвердить что ты не робот

Избегайте дополнительных статических переменных для связанных объектов.

При использовании связанных объектов, доступна функция времени исполнения Objective-C, начиная с iOS 4 и OSX 10.6, необходимо определить ключ для хранения и извлечения объекта во время выполнения.

Типичное использование - это определение ключа, следующего за

static char const * const ObjectTagKey = "ObjectTag";

а затем использовать для хранения объекта

objc_setAssociatedObject(self, ObjectTagKey, newObjectTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

и получить его

objc_getAssociatedObject(self, ObjectTagKey);

(пример, сделанный http://oleb.net/blog/2011/05/faking-ivars-in-objc-categories-with-associative-references/)

Существует ли более чистый способ определения связанного ключа объекта, который не включает объявление дополнительных переменных?

4b9b3361

Ответ 1

В соответствии с этой записью блога Эрикой Садун (чьи кредиты переходят на Gwynne Raskind), есть.

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

Оказывается, реализация @selector обеспечивает только то, что нам нужно, поскольку оно использует фиксированные адреса.

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

Итак, если вы связываете во время выполнения свойство вроде

@property (nonatomic, retain) id anAssociatedObject;

мы можем предоставить динамические реализации для своего геттера/сеттера, которые выглядят как

- (void)setAnAssociatedObject:(id)newAssociatedObject {
     objc_setAssociatedObject(self, @selector(anAssociatedObject), newAssociatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

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

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

Безопасно ли это?

Поскольку это зависит от реализации, законный вопрос: легко ли он сломается? Цитирование записи в блоге

Apple, вероятно, придется реализовать совершенно новый ABI, чтобы это произошло

Если мы возьмем эти слова как истинные, тогда они будут разумно безопасными.

Ответ 2

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

static void const *MyAssocKey = &MyAssocKey;

Если вам нужен только доступ из одного метода, вы можете просто использовать _cmd, который гарантированно будет уникальным. Например:

objc_setAssociatedObject(obj, _cmd, associatedObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

Ответ 3

Небольшое отклонение от идеи @Gabriele Petronella заключается в том, чтобы связать словарь с каждым объектом:

//NSObject+ADDLAssociatedDictionary.h

#import <Foundation/Foundation.h>

@interface NSObject (ADDLAssociatedDictionary)
- (void)addl_setAssociatedObject:(id)object forKey:(id<NSCopying>)key;
- (id)addl_associatedObjectForKey:(id<NSCopying>)key;
@end

//NSObject+ADDLAssociatedDictionary.m

#import <objc/runtime.h>

@interface NSObject (ADDLAssociatedDictionaryInternal)
- (NSMutableDictionary *)addl_associatedDictionary;
@end

@implementation NSObject (ADDLAssociatedDictionary)

- (void)addl_setAssociatedObject:(id)object forKey:(id<NSCopying>)key
{
    if (object) {
        self.addl_associatedDictionary[key] = object;
    } else {
        [self.addl_associatedDictionary removeObjectForKey:key];
    }
}
- (id)addl_associatedObjectForKey:(id<NSCopying>)key
{
    return self.addl_associatedDictionary[key];
}

@end

@implementation NSObject (ADDLAssociatedDictionaryInternal)
const char addl_associatedDictionaryAssociatedObjectKey;
- (NSMutableDictionary *)addl_associatedDictionaryPrimitive
{
    return objc_getAssociatedObject(self, &addl_associatedDictionaryAssociatedObjectKey);
}
- (void)addl_setAssociatedDictionaryPrimitive:(NSMutableDictionary *)associatedDictionary
{
    objc_setAssociatedObject(self, &addl_associatedDictionaryAssociatedObjectKey, associatedDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)addl_generateAssociatedDictionary
{
    NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init];
    [self addl_setAssociatedDictionaryPrimitive:associatedDictionary];
    return associatedDictionary;
}

- (NSMutableDictionary *)addl_associatedDictionary
{
    NSMutableDictionary *res = nil;

    @synchronized(self) {
        if (!(res = [self addl_associatedDictionaryPrimitive])) {
            res = [self addl_generateAssociatedDictionary];
        }
    }

    return res;
}
@end

Тогда в нашей категории на каком-то подклассе Производится NSObject

//Derived+Additions.h

#import "Derived.h"

@interface Derived (Additions)
@property (nonatomic) id anAssociatedObject;
@end

//Derived+Additions.m

#import "NSObject+ADDLAssociatedDictionary.h"

@implementation Derived (Additions)
- (void)setAnAssociatedObject:(id)anAssociatedObject
{
    [self addl_setAssociatedObject:anAssociatedObject forKey:NSStringFromSelector(@selector(anAssociatedObject))];
}
- (id)anAssociatedObject
{
    return [self addl_associatedObjectForKey:NSStringFromSelector(@selector(anAssociatedObject))];
}
@end

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

Преимущество, особенно для использования

NSStringFromSelector(@selector(anAssociatedObject))

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