Использование objc setAssociatedObject со слабыми ссылками
Я знаю, что OBJC_ASSOCIATION_ASSIGN существует, но обнуляет ли он ссылку, если целевой объект освобожден? Или это как в старые времена, когда эта ссылка должна быть сведена к нулю, или мы рискуем получить плохой доступ позже?
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, и достичь того же эффекта, что путь. Немного клуджи, но это сработает.