A healthy respect towards unowned
I often ask engineers I’m interviewing how they would use the unowned
keyword in Swift. Most of the responses can be categorized into the following:
- Ignorance: I never used
unowned
before. - Fear: I used
unowned
before, but it lead to crashes, so I never use it anymore. - Madness: I use
[unowned self]
in every closure. - Enlightenment: I would use it in these and these scenarios.
Personally, I find the first three categories of responses unacceptable from a senior engineer. This article is my attempt to guide you towards enlightenment.
Prerequisite
Before you continue reading, it is requires to have at least a basic understanding of references in Swift and about Automatic Reference Counting. You can read my other article about Retain Cycles in Swift for that.
weak
vs unowned
As a quick recap, neither weak
nor unowned
increases the reference count, so they can both be used to solve retain cycles.
There are two major differences, however:
weak
must be an optional,unowned
can be either non-optional or optionalweak
must be a variable,unowned
can be either a constant (let
) or a variable (var
)
The requirements imposed by weak
are because its value has to be modifiable at runtime, and must accept nil
. In case the references object gets deallocated, the Swift runtime will automatically set the reference to nil
.
On the other hand, unowned
does not impose these requirements, because the Swift runtime will not update it, if the referenced object gets deallocated.
And here lies the biggest benefit to unowned
:
- It can be non-optional
- It can be a constant
But it also has one drawback. If you dereference it (read its value), but the underlying object got deallocated, your app will crash with a nice message in the console: Fatal error: Attempted to read an unowned reference but the object was already deallocated
. This is what turns most people away.
However, unlike how most people describe it, the mere existence of the unowned
keyword in your codebase will not cause your app to crash and burn down in flames… Similarly to a chef’s knife, it can be the perfect tool for the job, or something that cuts your finger.
The easiest way to understand it, is to think of unowned
as an implicitly unwrapped weak
.
As such, these 2 pieces of code produce the same crash:
|
|
|
|
When to use which?
NOTE: I will only cover the topic from a code correctness perspective. There is also a performance aspect, that I will not cover, since if you are thinking about that, this article is likely below your knowledge level :)
Between class instances
The principle for deciding if you should use weak
or unowned
is to think if nil
makes sense:
- If the reference being
nil
is an expected scenario, useweak
- If the reference should never be
nil
, useunowned
These principles and the following scenarios only apply if your classes are not backed by a database framework. If you are using CoreData or Realm, they take care of retain cycles for you.
weak
reference
Let’s imagine a potential scenario involving a rental agency. You would like to keep track of apartments and their tenants.
You could do something like:
|
|
However, as soon as the first relationship is set up, it creates a retain cycle.
|
|
To determine if you should use weak
or unowned
, you should look at your business requirements:
- An apartment may a tenant, but it can also be vacant.
- A person may have an apartment, but it’s not requires (ex: a potential customer who is just visiting apartments)
As such, john.apartment
being nil
is an expected scenario, so weak
is the most appropriate, and Person
class should be updated to:
|
|
unowned
reference
Continuing the scenario, imagine you receive a new business requirement. You need to keep track, if your tenants have a dog or not.
The business requirement is that:
- A person may have a dog
- A dog must have an owner
Notice, that a dog without owner should never exist. As such, unowned
is the better option for this scenario, and the code would look like:
|
|
In this scenario, you can see the benefits of using unowned
instead of weak
. If you simply used weak
you would have two problems:
weak
would force you to makeowner
an optional. Any subsequent code you write that uses an instance ofDog
, you will always have to unwrap it, even though you knowdog.owner
is “nevernil
”weak
would force you to makeowner
avar
. Any subsequent code you write (or your colleague writes), can always dodog.owner = nil
, because someone might not remember the business requirements. And this has a knock-on effect on your earlier assumption of “it is nevernil
”
unowned
reference paired with implicitly unwrapped optional
There is another special scenario, where the lifetime of objects is tied together. For example, you cannot have an apartment without an address, and you cannot have an address without an apartment.
In this scenario, it’s most appropriate to use an implicitly unwrapped optional, paired with an unowned
:
|
|
I have often seen this situation in two cases:
- The Coordinator pattern, where each Screen has a Coordinator, and the Coordinator owns the Screen
- Architecture patterns like VIPER, where a module gets split into multiple objects, whose lifetime are the same
Closures
The principle for deciding if you need [self]
, [weak self]
or [unowned self]
is:
[self]
is appropriate when the closure is not stored indefinitely, and delaying the deallocation ofself
is not a problem[weak self]
is appropriate when the closure can outliveself
and delaying the deallocation ofself
is a problem[unowned self]
is appropriate when the closure will not outliveself
[self]
in closures
This means capturing self strongly. I recommend using this as the default option, unless you are not using frameworks like Combine, RxSwift or other heavily closure-based frameworks in your project.
This applies to most UIKit
and Foundation
methods, for example UIView.animate(withDuration: animations:)
or URLSession.dataTask(with:completionHandler:)
.
This is because the closure will only delay the deallocation of self
until the closure is executed. In most situations, this is not a problem and you do not have to worry about the potential of some logic not being executed, because self
gets deallocated.
You can further read about it in my other article: [weak self] is not always the solution.
[weak self]
in closures
Capturing self
as a weak
reference should be used when you know the closure is marked @escaping
, meaning could outlive self
, and you do not want to delay self
’s deallocation by capturing it strongly.
For example, suppose you want to show a loading indicator, then hide it after 3 seconds. You would do it something like:
|
|
In this case, if the user dismisses the screen, there is no point in stopping the animation, because the user doesn’t see it anyway. So, there’s no reason to keep self
in memory for an extra few seconds by capturing it strongly. As such, [weak self]
is the most appropriate option.
This applies to any scenario where delays are involved, be it fixed time or unknown duration (ex: network latency).
Despite how abundant the usage of [weak self]
is, I cannot come up with any other reasonable scenario. This is because, if the closure is stored indefinitely, then it should be tied to the lifetime of self
, and as such [unowned self]
is more appropriate.
Put it another way, a closure, that would do nothing when self
deallocates, should not outlive self
in the first place.
[unowned self]
in closures
Capturing self
as an unowned
reference should be used when you know that the lifetime of self
is identical or longer than the lifetime of the closure.
A good example is the new UIAction
based methods added to UIControl
in iOS 14:
Suppose you want to increment a counter every time the button is tapped. With this new method, you would write it something like:
|
|
Unfortunately, This would cause a retain cycle.
If you were to use [weak self]
, you would then have to do the whole guard let self = self else { return }
dance, just in case™.
But if you stop and think about it:
- The closure captures
self
- The closure is owned by
UIAction
- The
UIAction
is owned by theUIButton
- The
UIButton
is owned byself.view
self.view
is owned byself
(the View Controller)
If self
gets deallocated, self.view
gets deallocated, which means the UIButton
gets deallocated, which means UIAction
gets deallocated, which means the closure gets deallocated. So there is never a scenario where the closure is alive and self
is not!
As such, it’s much better to use [unowned self]
in this situation.
Closures in Functional Reactive Programming
FRP Frameworks, like RxSwift and Combine make heavy use of closuring. This is probably the #1 cause of retain cycles, most probably because they are newer concepts, and engineers are less familiar with it.
These frameworks play the nicest when you only use values from inside the chain and avoid capturing self
altogether. However, if you are not yet using it throughout your whole project, you will eventually have to work around this.
Suppose you have a button, and a non-reactive property that tells you if the user is logged in. You would like to increment a counter every time the button is tapped, but only when the user is logged in.
Your code could look something like this:
|
|
or with Combine
:
|
|
The rule of thumb is, if you are storing the reactive chain in your DisposeBag
or Set<AnyCancellable>
, then you should use [unowned self]
instead of [weak self]
.
This is for the same reason as you’ve seen earlier:
- The closure holds
self
DisposeBag
orSet<AnyCancellable>
holds the closureself
holdsDisposeBag
orSet<AnyCancellable>
If self
were to get deallocated, DisposeBag
or Set<AnyCancellable>
would also get deallocated, thus the closure would also get deallocated, and never be called.
This has the benefit of not having to do the whole guard let self = self else { return }
dance, which can be particularly annoying in methods like map
or flatMap
.
NOTE: RxSwift has an onDisposed
argument in subscribe(onNext:onError:onCompleted:onDisposed)
. You should not use [unowned self]
here, because it could be called after self
has been disposed.
NOTE: RxSwift 6 introduced subscribe(with:onNext:onError:onCompleted:onDisposed:)
that you can also use.
Conclusion
Congratulations if you’ve made it this far! It was a pretty long and complex article.
To recap:
When talking about properties:
- If the reference being
nil
is an expected scenario, useweak
- If the reference should never be
nil
, useunowned
- If you have two objects that should not exist without each other, use
unowned
reference paired with implicitly unwrapped optional
When talking about closures:
- Without FRP frameworks involved:
- Prefer
[self]
as the default option, if you want the code inside the closure to always run, thus delay the deallocation ofself
- Use
[weak self]
if the closure is@escaping
, and it’s not important enough to delay the deallocation ofself
- Use
[unowned self]
if the closure cannot outliveself
- Prefer
- With FRP frameworks involved:
- Use
[unowned self]
when the chain is stored in yourDisposeBag
orSet<AnyCancellable>
- Use
If you are still defensive about it, thinking “Always use weak
, never unowned
, because it’s safer™”, I think it’s better to use the right tool for the job. After all, you didn’t stop using your chef’s knife after you cut your finger once, did you?
I would also recommend reading Automatic Reference Counting documentation on swift.org
about this subject.
If there is anything unclear or you think anything could be explained better, I would love to hear your thoughts and suggestions. Feel free to get in touch at moc.htikmsofi@gomlb!