[weak self] is not always the solution
Tags: swift, ios, tips and tricks
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
[weak self]
!
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.
Problem
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.
When the completion
closure is called, self
will be nil
, so unwrapping will fail and nothing inside the closure will be run.
Solution
You could solve this in two ways:
- You could simply capture
self
strongly, since you know thatApiService
does not retain the closure, soself
will 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 onlylocalCache
in this case.
|
|
This scenario is safe even when apiService
retains the completion
closure, because there are no circular references. self
owns apiService
, who owns completion
, who owns localCache
. But localCache
does not own anyone in the chain, so there isn’t a cycle.
Alternatives to [weak self]
1. Capture self
strongly
As you’ve seen in the first example, sometimes capturing self
strongly is safe.
This can be used in scenarios when:
self
does not hold a reference to the callee
|
|
- The closure is not
@escaping
.
|
|
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 localCache
or otherService
, the closure will still hold a reference to the old instance.
Conclusion
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!