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?
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.