dealloc中将弱引用属性设置为nil,但是该属性的实例变量不为nil。

14

我注意到在启用ARC的Objective-C中有以下情况:

假设我们有一个简单的类A和自动生成的弱引用属性。

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

@implementation A
@end

并且有实现dealloc方法的B类,属于第二类

@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”。有任何想法为什么会发生这种情况?

也许当弱引用应该被清除时,访问方法会返回 nil,但实例变量本身保持不变(并被释放,现在它是一个悬空指针)。 - user529758
我认为在dealloc中,对象仍然有效且尚未被删除。例如,您可以从NSNotificationCenter中注销它并访问其属性。 - plamkata__
“你可以访问它”并不一定意味着它没有被释放,但我可能是错的。 - user529758
1个回答

26

简短回答:-[B dealloc]方法中,实例变量a->_refObject尚未为nil, 但是每次访问该弱引用的操作都是通过ARC运行时函数完成的,如果对象已经开始被释放,则返回nil。

详细回答:通过设置观察点,您可以看到在解除分配过程的末尾,a->_refObject被设置为nil。 当命中观察点时,堆栈回溯如下所示:

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]中,在(由编译器生成的)[super dealloc]被调用之前,a->_refObject不为nil。

所以问题仍然存在: 为什么此时a.refObject返回nil?

原因是对于每个弱指针访问,ARC编译器都会生成一个objc_loadWeak()objc_loadWeakRetained()的调用。根据文档

 

id objc_loadWeakRetained(id *object)

    

如果对象已注册为__weak对象,并且最后一个存储在对象中的值尚未被解除引用或开始解除引用,则保留该值并返回它。否则返回null。

因此,即使此时a->refObject不为nil,通过objc_loadWeakRetained()访问弱指针(如属性访问器方法所做的那样)也会返回nil,因为B对象的解除引用已经开始。

调试器直接访问a->refObject,并不调用objc_loadWeak()


谢谢。我认为每次访问弱指针时,编译器都会添加objc_loadWeak,但似乎对于a->_refObj并非如此。 - plamkata__
@plamkata__:编译器 为每次访问 a->refObject 添加 objc_loadWeak[Retained],只有 调试器 直接访问实例变量。 - Martin R
非常有道理。我原本以为我在dealloc方法中使用NSLog来转储ivar并且这让我感到困惑。但实际上我并没有使用NSLog而是使用调试器,所以正如你所提到的,它可以直接访问它。再次感谢您的帮助。 - plamkata__

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接