Is the weakSelf/strongSelf dance really necessary when referencing self inside a non-retained completion called from a UIViewController?

10,686

Solution 1

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.

Solution 2

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.

Solution 3

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

Solution 4

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.

Share:
10,686
Robert Atkins
Author by

Robert Atkins

Updated on June 09, 2022

Comments

  • Robert Atkins
    Robert Atkins almost 2 years

    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.

  • Robert Atkins
    Robert Atkins over 10 years
    Can you speak to the specifics of the UIViewController possibly being retained longer than necessary and whether that matters? That's the real meat of the question. Links to authoritative treatments (NSHipster, Mike Ash, objc.io, @bbum etc) especially appreciated :-).
  • Robert Atkins
    Robert Atkins over 10 years
    In this particular context, I wrote the NetworkService, so I know it doesn't retain the block. I understand I can't rely upon this if I don't have visibility into that code.
  • Abizern
    Abizern over 10 years
    My point is that this is what you should be thinking about when considering whether you need to use a weakSelf construct.
  • eofster
    eofster over 10 years
    Why would UIViewController be retained longer than necessary? What was new in iOS 6 is that view controllers don’t unload their views automatically. It has nothing to do with retaining view controller objects themselves.
  • Rob
    Rob over 10 years
    @AlexeiKuznetsov If the user dismisses the view controller while the network operation is still in progress, if the network operation's completion block references the view controller, self, the view controller won't get released until the network operation completes. While often not a significant problem, it could be exacerbated if you had a whole bunch of network operations backlogged or if it's a very lengthy network operation. There's no reason for network operation to maintain strong reference to view controller, so weakSelf pattern is advisable.
  • eofster
    eofster over 10 years
    In the simplest case where there is only a call to self in the callback, yes, it would be easier. But if the callback does something else in addition to referencing self, there might be side-effects because the programmer might not expect self to suddenly become nil and the controller to not exist anymore. So to avoid those cases one probably needs to check the current state of the controller anyway. What do you think?
  • Rob
    Rob over 10 years
    BTW, if your network operations are time consuming (either because of size or number of requests) and you don't really want them to continue executing after the view controller is dismissed, in addition to the weakSelf pattern, you might also contemplate making your network operations cancelable, and have the view controller cancel any pending requests when it's dismissed. Using operation queue with cancelable NSOperation-based requests, rather than GCD, can facilitate that process.
  • Rob
    Rob over 10 years
    @AlexeiKuznetsov If the developer has employed the weakSelf pattern (which is designed explicitly to handle the view controller being deallocated), I'm not sympathetic to the "might not expect self to suddenly become nil" theory. But if the developer is doing a whole bunch of things and wants to be assured that once the completion block starts that the weakSelf won't get deallocated in some race condition, then he might employ the more cumbersome weakSelf/strongSelf pattern, checking to see if strongSelf is not nil. But, in most cases, weakSelf, alone, is sufficient.
  • Gon
    Gon about 9 years
    I thought you were right until I wrote a test code, which perform block in a AFNetworking method as following: turn off network, push view controller to request with AFNetworking, pop view controller, turn on network. No matter self or weakSelf I use, the block is performed anyway. So I guess when you perform the method using block, the self is captured and strong retained by the block object. So no use for the weakSelf trick.
  • Rob
    Rob about 9 years
    First, weakSelf pattern doesn't affect whether block runs or not. It runs unless you cancel it. Second, weakSelf simply dictates that the block will not maintain strong reference to self (but obviously, whether it is deallocated or not is a question as to whether all other strong references are resolved, too.) If you have example that you think violates the well-established weakSelf pattern, post your own question with MCVE.
  • Gon
    Gon about 9 years
    yes weakSelf in your example code doesn't stop the block from running even the view controller self pop, which you said it would. My guess is because self.networkService still retain self.
  • Rob
    Rob about 9 years
    The networkService would not have any strong reference cycle of its own as a result of this block. Now, sometimes (perhaps even frequently) network objects are designed to retain themselves for the duration of some request (that's a very useful pattern), but that's an internal implementation detail of networkService itself and has nothing to do with the above. But this code shown above would not maintain a strong reference to networkService.
  • Gon
    Gon about 9 years
    yes there's no retain cycle. My point is using weakSelf doesn't make sure that the view controller dealloc when you pop. There're other conditions like self.networkService need to think about.
  • Rob
    Rob about 8 years
    Yes, because you use weakSelf pattern here doesn't ensure that that there are no other unrelated strong references elsewhere. It only ensures that the block, itself, doesn't maintain that strong reference. And, just to be perfectly clear, referencing self.networkService does not maintain any strong reference to self for the duration of the network request. It's only references to self inside the block will do that (or if you're using delegate patterns, and strong delegate references).