Слабое свойство установлено равным нулю в dealloc, но свойство ivar не равно нулю

Я заметил следующее в Objective-C с включенным ARC:

Пусть имеет простой класс A и автосинтезированное слабое свойство

@interface A
@property (nonatomic, weak) id refObject;
@end

@implementation A
@end

И второй класс B с реализованным dealloc

@interface B
@end

@implementation B
-(void) dealloc
{
    NSLog(@"In dealloc");
}
@end

И, наконец, где-то в классе A есть следующее:

@implementation A
...
-(void) foo
{
   B* b = [B new];
   self.refObject = b;
   // Just use b after the weak assignment
   // in order to not dealloc 'b' before assignement 
   NSLog(@"%@", b);
}
...
@end 

Если я устанавливаю точку останова в [B dealloc] и проверяю свойство [A refObject], я могу видеть, что a.refObject равен nil, но a->_refObject не равен nil и указывает на 'b'

Любые идеи, почему это происходит?

Ответ 1

Краткий ответ: Переменная экземпляра a->_refObject пока еще не указана в -[B dealloc], но каждый доступ к этому слабому указателю выполняется через функцию времени выполнения ARC который возвращает nil, если освобождение уже началось.

Длинный ответ:. Устанавливая точку наблюдения, вы можете видеть, что a->_refObject установлен на нуль в конце процесс освобождения. Возврат стека (при ударе точки наблюдения) выглядит следующим образом:

frame #0: 0x00007fff8ab9f0f8 libobjc.A.dylib`arr_clear_deallocating + 83
frame #1: 0x00007fff8ab889ee libobjc.A.dylib`objc_clear_deallocating + 151
frame #2: 0x00007fff8ab88940 libobjc.A.dylib`objc_destructInstance + 121
frame #3: 0x00007fff8ab88fa0 libobjc.A.dylib`object_dispose + 22
frame #4: 0x0000000100000b27 weakdealloc`-[B dealloc](self=0x000000010010a640, _cmd=0x00007fff887f807b) + 151 at main.m:28
frame #5: 0x0000000100000bbc weakdealloc`-[A foo](self=0x0000000100108290, _cmd=0x0000000100000e6f) + 140 at main.m:41
frame #6: 0x0000000100000cf5 weakdealloc`main(argc=1, argv=0x00007fff5fbff968) + 117 at main.m:52
frame #7: 0x00007fff8c0987e1 libdyld.dylib`start + 1

и object_dispose() вызывается из -[NSObject dealloc] (как видно из http://www.opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm).

Поэтому в -[B dealloc], a->_refObject не равен нулю перед вызовом (генерируется компилятор) [super dealloc].

Итак, остается вопрос: почему a.refObject возвращает нуль в этой точке?

Причина в том, что для каждого доступа к слабому указателю создается компилятор ARC вызов objc_loadWeak() или objc_loadWeakRetained(). Из документа :

id objc_loadWeakRetained (id * object)

Если объект зарегистрирован как объект __weak, а последнее значение, хранящееся в объекте, еще не было освобождено или начато освобождение, оно сохраняет это значение и возвращает его. В противном случае > возвращает значение null.

Так что даже если a->refObject не является нулем в этой точке, доступ к слабой стрелке через objc_loadWeakRetained() (как это делает метод доступа к свойствам) возвращает nil, потому что освобождение объекта B уже началось.

Отладчик напрямую обращается к a->refObject и не вызывает objc_loadWeak().