What happens if I send a message to a weak object? Does sending the message possess the object and hold it in memory until return?
I'm thinking of this pattern:
__weak MyObject *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf doSomeAction];
});
Assuming weakSelf
is non-nil when the message is sent, might it be deallocated while doSomeAction
is working or is it guaranteed to remain valid until doSomeAction
returns?
From the Clang ARC documentation:
Reading occurs when performing a lvalue-to-rvalue conversion on an object lvalue.
- For
__weak
objects, the current pointee is retained and then released at the end of the current full-expression. This must execute atomically with respect to assignments and to the final release of the pointee.
Messaging a weak reference performs an lvalue-to-rvalue conversion on the variable, which means the value of the weak reference will be retained and then released at the end of the current full-expression (basically, the statement). It's basically equivalent to assigning to a strong variable whose scope only lasts for the current statement, and then messaging that strong variable.
The takeaway here is if you want to message a weak variable once, and never touch it again, and you don't care about the side-effects of evaluating the arguments to the method in the case where the weak reference ends up nil
, then go ahead and message the weak reference directly. But if you need to refer to the weak reference twice (in separate statements), or the side-effects of evaluating the arguments do matter, then you should assign to a strong variable and test for non-nil
before proceeding.
You asked:
Assuming weakSelf
is non-nil
when the message is sent, might it be deallocated while doSomeAction
is working or is it guaranteed to remain valid until doSomeAction
returns?
Yes, not only does it remain valid until doSomeAction
returns, it is retained for the rest of the block, too. Consider the following:
- (void)dealloc
{
NSLog(@"%s", __FUNCTION__);
}
- (void)startBackgroundOperation
{
__weak MyObject * weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf doSomeAction];
sleep(5);
[weakSelf doSomeAction2];
});
sleep(1);
}
- (void)doSomeAction
{
NSLog(@"%s", __FUNCTION__);
}
- (void)doSomeAction2
{
NSLog(@"%s", __FUNCTION__);
}
In this example, I make sure that the object was in scope when the block starts, but let it fall out of scope between doSomeAction
and doSomeAction2
, but the block appears to retain it for the completion of the block. But if I comment out the invocation of doSomeAction
, the weak
reference is nil
by the time it gets to doSomeAction2
, just as you'd expect.
As an aside, in WWDC 2011 - #322 - Objective-C Advancements In-Depth, they point out (about 27:00 min into the video), they point out that if you're dereferencing weakSelf
, you should have a local strong reference inside the dispatch block to protect yourself in race condition, thus:
__weak MyClass *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
MyClass *strongSelf = weakSelf;
if (strongSelf)
[strongSelf->myView doSomeViewAction];
});
That will retain it for the duration of the block (assuming it wasn't already deallocated by the time the block was executed).