Weak property not zeroing using ARC

2019-01-28 16:57发布

I have the following simple code for an object that holds a weak reference:

// interface

@interface GMWeakRefObj : NSObject
@property (weak) id object;
@end

// implementation

@implementation GMWeakRefObj 
@synthesize object;
@end

When I run the following test code it fails on the second assert:

NSData* d = [[NSData alloc] init];
GMWeakRefObj* weakRef = [[GMWeakRefObj alloc] init];
weakRef.object = d;
NSAssert(weakRef.object != nil, @"Reference wasn't assigned");
d = nil;
NSAssert(weakRef.object == nil, @"Reference wasn't zeroed"); // <-- FAIL

Aren't ARC weak references supposed to be zeroing? And if so what am I doing wrong?

1条回答
乱世女痞
2楼-- · 2019-01-28 17:23

Try some custom class class instead of NSData for d, e.g. MyData. Implement dealloc method in it and set breakpoint in it. You will see, that dealloc is called by autorelease pool after the last NSAssert. Only after that week reference will become nil.

ADD: Looks like I have to extend my answer to make it clear, why it works that way. First, lets look at your example (from comments):

NSData* data = [[NSData alloc] init];
__weak NSData* weakRef = data;
data = nil;
NSAssert(weakRef == nil, @"Failed to zero");

It works as expected, weakRef become nil after data = nil. The next example works too:

NSData* data = [[NSData alloc] init];
__weak NSData* weakRef = data;
NSLog(@"%@", data);
data = nil;
NSAssert(weakRef == nil, @"Failed to zero");

But the last example doesn't work:

NSData* data = [[NSData alloc] init];
__weak NSData* weakRef = data;
NSLog(@"%@", weakRef);
data = nil;
NSAssert(weakRef == nil, @"Failed to zero");

The only difference is that we use weak reference to output log. Why?

(the rest of the answer can be wrong :) )

Imaging that you NSLog (or any other function/selector we call before data = nil) rely on it's argument not to be nil. For example, it has "if (arg == nil) return;" at the very beginning.

In multithreaded environment weak reference can become nil after if.

So properly written function should look like:

  // ...
  T* local_arg = arg;   // NOTE: it is strong!
  if (local_arg == nil)
    return;
  // work with local_arg here, not with arg
  // ...

But usually we don't want to do it everywhere -- it will be ugly. So we want to be sure that arguments will not disappear somewhere in the middle. Compiler does it for us by autoreleasing weak reference.

So, it should be clear how, why your GMWeakRefObj test case doesn't work -- weakRef is autoreleased before calling setObject setter.

查看更多
登录 后发表回答