Is the weakSelf/strongSelf dance really necessary when referencing self inside a non-retained completion called from a UIViewController?
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:
__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, likeUIViewController
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 cancelNSURLConnection
, it's not onlyNSOperation
that can be canceled, you can cancel whatever is being executed asynchronously in the method that calls back to block.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 byUINavigationController
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 inUINavigationControllerDelegate
methods by cancelling asynchronous tasks from the mutable collection that reside either onUINavigationController
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.
Robert Atkins
Updated on June 09, 2022Comments
-
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 theUIViewController
instance being retained by the block. As long asperformAsyncNetworkCallWithCompletion
does not store the block in a property (or ivar) on myNetworkService
, 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 myUIViewController
at all (after the changes to the way iOS 6 manages aUIViewController
's backingCALayer
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 over 10 yearsCan 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 over 10 yearsIn 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 over 10 yearsMy point is that this is what you should be thinking about when considering whether you need to use a
weakSelf
construct. -
eofster over 10 yearsWhy 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 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, soweakSelf
pattern is advisable. -
eofster over 10 yearsIn 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 over 10 yearsBTW, 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 cancelableNSOperation
-based requests, rather than GCD, can facilitate that process. -
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 becomenil
" theory. But if the developer is doing a whole bunch of things and wants to be assured that once the completion block starts that theweakSelf
won't get deallocated in some race condition, then he might employ the more cumbersomeweakSelf
/strongSelf
pattern, checking to see ifstrongSelf
is notnil
. But, in most cases,weakSelf
, alone, is sufficient. -
Gon about 9 yearsI 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
orweakSelf
I use, the block is performed anyway. So I guess when you perform the method using block, theself
is captured and strong retained by the block object. So no use for the weakSelf trick. -
Rob about 9 yearsFirst,
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 toself
(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-establishedweakSelf
pattern, post your own question with MCVE. -
Gon about 9 yearsyes
weakSelf
in your example code doesn't stop the block from running even the view controllerself
pop, which you said it would. My guess is becauseself.networkService
still retain self. -
Rob about 9 yearsThe
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 ofnetworkService
itself and has nothing to do with the above. But this code shown above would not maintain a strong reference tonetworkService
. -
Gon about 9 yearsyes 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 likeself.networkService
need to think about. -
Rob about 8 yearsYes, 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, referencingself.networkService
does not maintain any strong reference toself
for the duration of the network request. It's only references toself
inside the block will do that (or if you're using delegate patterns, and strong delegate references).