Использование objc setAssociatedObject со слабыми ссылками


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

4 15

4 ответа:

Как продемонстрировал ультрамиракулус, OBJC_ASSOCIATION_ASSIGN не делает обнуление слабой ссылки, и вы рискуете получить доступ к освобожденному объекту. Но это довольно легко реализовать самому. Вам просто нужен простой класс, чтобы обернуть объект со слабой ссылкой:

@interface WeakObjectContainer : NSObject
@property (nonatomic, readonly, weak) id object;
@end

@implementation WeakObjectContainer
- (instancetype) initWithObject:(id)object
{
    if (!(self = [super init]))
        return nil;

    _object = object;

    return self;
}
@end

Тогда вы должны связать WeakObjectContainer как OBJC_ASSOCIATION_RETAIN (_NONATOMIC):

objc_setAssociatedObject(self, &MyKey, [[WeakObjectContainer alloc] initWithObject:object], OBJC_ASSOCIATION_RETAIN_NONATOMIC);

И использовать свойство object для доступа к нему, чтобы получить обнуляющую слабую ссылку:

id object = [objc_getAssociatedObject(self, &MyKey) object];

Еще один вариант, аналогичный WeakObjectContainer:

- (id)weakObject {
    id (^block)() = objc_getAssociatedObject(self, @selector(weakObject));
    return (block ? block() : nil);
}

- (void)setWeakObject:(id)object {
    id __weak weakObject = object;
    id (^block)() = ^{ return weakObject; };
    objc_setAssociatedObject(self, @selector(weakObject),
                             block, OBJC_ASSOCIATION_COPY);
}

Попробовав его, я получаю отрицательный ответ.

Я запустил следующий код в симуляторе iOS 6, но он, вероятно, будет иметь такое же поведение с предыдущими итерациями среды выполнения:

NSObject *test1 = [NSObject new];

NSObject __weak *test2 = test1;

objc_setAssociatedObject(self, "test", test1, OBJC_ASSOCIATION_ASSIGN);

test1 = nil;

id test3 = objc_getAssociatedObject(self, "test");

В конце концов, test1 и test2 равны нулю, а test3-это указатель, ранее сохраненный в test1. Использование test3 приведет к попытке получить доступ к объекту, который уже был освобожден.

Это поведение не указано в документах или заголовках, насколько я могу судить, поэтому, вероятно, это деталь реализации, на которую вы не должны рассчитывать, даже если вы смогли различить текущее поведение. Я бы предположил, что он Не обнулен. Вот почему:

В общем случае нет необходимости nil выводить ссылки в iVars во время -dealloc. Если объект освобожден, не должно иметь значения, если его ивары были обнулены, потому что любой дальнейший доступ к освобожденному объекту или его Ивар сам по себе является программной ошибкой. На самом деле, я слышал, как некоторые утверждают, что лучше не очищать ссылки во время -dealloc, потому что это сделает ошибочные обращения более очевидными/выявлять ошибки раньше.

EDIT: О, я думаю, что неправильно понял ваш вопрос. Вы хотите "обнулить слабые ссылки". Связанное хранилище, похоже, не поддерживает их. Вы можете создать тривиальный сквозной класс с одним свойством ivar/, помеченным как _ _ weak, и достичь того же эффекта, что путь. Немного клуджи, но это сработает.