Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Messaging a __weak object?

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?

like image 452
Steven Fisher Avatar asked Feb 22 '13 21:02

Steven Fisher


Video Answer


2 Answers

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.

like image 98
Lily Ballard Avatar answered Sep 19 '22 03:09

Lily Ballard


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?

This ARC behavior has changed over time. But nowadays, weak references can be released as soon as the last strong reference is removed.

Thus, consider the following:

- (void)dealloc {
    NSLog(@"%s", __FUNCTION__);
}

- (void)startBackgroundOperation {
    __weak typeof(self) weakSelf = self;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf doSomeAction];
        [NSThread sleepForTimeInterval:5];
        [weakSelf doSomeAction2];
    });
}

- (void)doSomeAction {
    NSLog(@"%s", __FUNCTION__);
}

- (void)doSomeAction2 {
    NSLog(@"%s", __FUNCTION__);
}

If you have some code invoke startBackgroundOperation and let the object be deallocated in the intervening time between doSomeAction and doSomeAction2, you will see the former will be called and the latter will not. I.e. if there were no more strong references, the object could be deallocated in the middle of the block.

So, if you want weak reference, but want an “all or none” sort of behavior whereby it to be retained for the duration of the closure, we perform what is jokingly referred to as the “weakSelf-strongSelf dance”:

- (void)startBackgroundOperation {
    __weak typeof(self) weakSelf = self;

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        typeof(self) strongSelf = weakSelf;        // establish just-in-time strong reference (if `weakSelf` is not yet `nil`)

        [strongSelf doSomeAction];
        [NSThread sleepForTimeInterval:5];
        [strongSelf doSomeAction2];
    });
}

This will ensure that the block has a weak reference, but if it is not deallocated by the time it hits the assignment of strongSelf, then it will establish and maintain a strong reference for the duration of the block.


For what it is worth, this weakSelf-strongSelf pattern is essential when dereferencing ivars with -> (avoiding race conditions with weakSelf).

E.g.

- (void)badDeferenceExample {
    __weak typeof(self) weakSelf = self;

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if (!weakSelf) { return; }

        NSInteger value = weakSelf->_someIVar;     // this is race and can crash!!!

        ...
    });
}

- (void)properDeferenceExample {
    __weak typeof(self) weakSelf = self;

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        typeof(self) strongSelf = weakSelf;        // establish just-in-time strong reference (if `weakSelf` is not yet `nil`)

        if (!strongSelf) { return; }

        NSInteger value = strongSelf->_someIVar;   // this is safe

        ...
    });
}
like image 23
Rob Avatar answered Sep 22 '22 03:09

Rob