[weak self] is not always the solution
At a certain point in your iOS development journey, you probably encountered a retain cycle when working with closures, which you eventually solves by using
[weak self] in the capture list.
If this happens often enough, you might adopt a blanket rule such as:
All closures must have
Yes, this will solve your retain cycles, but it can introduce problems that are worse.
This happened very often with my colleagues, particularly when they are learning reactive frameworks, like RxSwift or Combine, since those make extensive use of closures.
Suppose you are building a social media app, and you want to locally cache a user’s friends list. When the user adds a new friend, you immediately add it to the cache to provide feedback to the user. In case the network request failed, you revert the change, so the cache has the correct state.
Your code might looks something like:
Can you think of a scenario where this code will not work correctly?
What will happen if the
FriendsListViewController gets deallocated while the network request is running? For example, the user dismisses the screen.
completion closure is called,
self will be
nil, so unwrapping will fail and nothing inside the closure will be run.
You could solve this in two ways:
- You could simply capture
selfstrongly, since you know that
ApiServicedoes not retain the closure, so
selfwill only be retained until the network request finishes
- Some people do not know this, but in fact, you can write other things than
[weak self]in the capture list! You could strongly capture only
localCachein this case.
This scenario is safe even when
apiService retains the
completion closure, because there are no circular references.
apiService, who owns
completion, who owns
localCache does not own anyone in the chain, so there isn’t a cycle.
As you’ve seen in the first example, sometimes capturing
self strongly is safe.
This can be used in scenarios when:
selfdoes not hold a reference to the callee
- The closure is not
2. Strongly capturing only what you need
You can even have multiple objects in your capture list!
However, you need to be careful. The objects in the capture list are read when the closure is created. The above code is identical to:
If between the time
addFriend is called and the
completion closure is called, a new instance is assigned to
otherService, the closure will still hold a reference to the old instance.
In this article, you’ve seen a scenario when using
[weak self] can cause problems. And you’ve also seen two alternatives that can be used.
Unfortunately, there is no silver bullet solution that can be applied to all scenarios.
Instead of trying to come up with a blanket rule, I recommend trying to understand how references work and how retain cycles happen. Then stopping and thinking about each scenario to see if a retain cycle is possible and figuring out which option is the best.
I would love to hear your thoughts and suggestions. Feel free to get in touch at moc.htikmsofi@gomlb!