39
votes

Say I have the following method inside a UIViewController subclass:

- (void)makeAsyncNetworkCall
{
    [self.networkService performAsyncNetworkCallWithCompletion:^{
        dispatch_async(dispatch_get_main_queue(), ^{
                [self.activityIndicatorView stopAnimating];
            }
        });
    }];
}

I know that the reference to self inside the block results in the UIViewController instance being retained by the block. As long as performAsyncNetworkCallWithCompletion does not store the block in a property (or ivar) on my NetworkService, am I right in thinking there is no retain cycle?

I realise this structure above will lead to the UIViewController being retained until performAsyncNetworkCallWithCompletion finishes, even if it is released by the system earlier. But is it likely (or even possible?) the system will deallocate my UIViewController at all (after the changes to the way iOS 6 manages a UIViewController's backing CALayer memory)?

If there is a reason I must do the "weakSelf/strongSelf dance", it would look like this:

- (void)makeAsyncNetworkCall
{
    __weak typeof(self) weakSelf = self;
    [self.networkService performAsyncNetworkCallWithCompletion:^{
        typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        dispatch_async(dispatch_get_main_queue(), ^{
                [strongSelf.activityIndicatorView stopAnimating];
            }
        });
    }];
}

But I find this unconscionably ugly and would like to avoid it if it's not necessary.

4
no need for strongSelf. if self has been deallocated, weakSelf will be nil, which is fine.Patrick Goley
Yes, correct in this example, though see the caveats at the end of the accepted answer.Robert Atkins

4 Answers

32
votes

As I believe you correctly diagnosed, using self will not necessarily cause strong reference cycle in this scenario. But this will retain the view controller while the network operation completes, and in this case (as in most cases), there's no need to. Thus, it may not be necessary to do use weakSelf, but probably prudent to do so. It minimizes the chance of an accidental strong reference cycle and leads to more efficient use of memory (releasing the memory associated with the view controller as soon as that view controller is dismissed rather than unnecessarily retaining the view controller until after the network operation is complete).

There is no need for the strongSelf construct, though. You can:

- (void)makeAsyncNetworkCall
{
    __weak typeof(self) weakSelf = self;
    [self.networkService performAsyncNetworkCallWithCompletion:^{
        dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf.activityIndicatorView stopAnimating];
        });
    }];
}

You only need the weakSelf/strongSelf combination where it's critical to have a strong reference (e.g., you're dereferencing ivars) or if you need to worry about race conditions. That does not appear to be the case here.

7
votes

I think the issue is that the networkService may keep a strong reference to the block. And the view controller may have a strong reference to the networkService. So the possible cycle of VC->NetworkService->block->VC could exist. However, in this case, it's usually safe to assume that the block will be released after it has run, in which case the cycle is broken. So, in this case, it isn't necessary.

Where it is necessary is if the block is not released. Say, instead of having a block that runs once after a network call, you have a block that is used as a callback. i.e. the networkService object maintains a strong reference to the block and uses it for all callbacks. In this case, the block will have a strong reference to the VC, and this will create a strong cycle, so a weak reference is preferred.

3
votes

No, If your self.networkService don't use it as a block property you should be fine

0
votes

The answer is not so straightforward here. I agree with @Rob's answer, but it needs additional explanation:

  1. __weak is considered as a safe way, since it nils the self when released, meaning there will be no exception if callback happens much later when the calling object is already released, referenced by block, like UIViewController popped from the stack. Adding the possibility of cancelling any kind of operation is her merely a matter of hygiene and perhaps resources as well. You can, for example also just cancel NSURLConnection, it's not only NSOperation that can be canceled, you can cancel whatever is being executed asynchronously in the method that calls back to block.

  2. If self is let to be retained by the block, then the story can get a bit complicated if the caller object like UIViewController is being released by UINavigationController and block still retains it and calls back. In this case callback block will be executed and assumed some data will be changed by results of it. That might be even wanted behaviour, but in most of the cases not. Therefore the cancelling of operation might be more vital in this case, making it very wise in UINavigationControllerDelegate methods by cancelling asynchronous tasks from the mutable collection that reside either on UINavigationController as associated object or as a singleton.

Save bet is with first option, of course, but only in the case you don't want asynchronous operation to continue after you dismiss the caller object.