A healthy respect towards unowned

Publish date: April 5, 2021
Tags: swift, ios

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:

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:

  1. weak must be an optional, unowned can be either non-optional or optional
  2. weak 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:

  1. It can be non-optional
  2. 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:

1
2
3
4
5
6
7
8
class WeakExperiment {
    weak var value: UIViewController?

    init() {
        self.value = nil
        print(self.value!) // Force-unwrapped `nil`.
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class UnownedExperiment {
    unowned let value: UIViewController

    init() {
        var underlyingValue: UIViewController? = UIViewController()
        self.value = underlyingValue!
        underlyingValue = nil
        print(self.value) // `unowned`, with its underlying reference deallocated
    }
}

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:

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Apartment {
    let number: Int
    var tenant: Person?

    init(number: Int) {
        self.number = number
    }
}

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
    }
}

However, as soon as the first relationship is set up, it creates a retain cycle.

1
2
3
4
let apartment = Apartment(number: 1)
let john = Person(name: "John")
apartment.tenant = john
john.apartment = apartment

To determine if you should use weak or unowned, you should look at your business requirements:

  1. An apartment may a tenant, but it can also be vacant.
  2. 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:

1
2
3
4
5
6
7
8
class Person {
    let name: String
    weak var apartment: Apartment?

    init(name: String) {
        self.name = name
    }
}

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:

  1. A person may have a dog
  2. 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Person {
    let name: String
    weak var apartment: Apartment?
    var dog: Dog?

    init(name: String) {
        self.name = name
    }
}

class Dog {
    unowned let owner: Person
    init(owner: Person) {
        self.owner = owner
    }
}

In this scenario, you can see the benefits of using unowned instead of weak. If you simply used weak you would have two problems:

  1. weak would force you to make owner an optional. Any subsequent code you write that uses an instance of Dog, you will always have to unwrap it, even though you know dog.owner is “never nil
  2. weak would force you to make owner a var. Any subsequent code you write (or your colleague writes), can always do dog.owner = nil, because someone might not remember the business requirements. And this has a knock-on effect on your earlier assumption of “it is never nil

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Apartment {
    let number: Int
    private(set) var address: Address!

    init(number: Int, postalAddress: String) {
        self.number = number
        self.address = Address(postalAddress: postalAddress)
    }
}

class Address {
    let postalAddress: String
    init(postalAddress: String) {
        self.postalAddress = postalAddress
    }
}

I have often seen this situation in two cases:

  1. The Coordinator pattern, where each Screen has a Coordinator, and the Coordinator owns the Screen
  2. 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] 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class ScreenWithActivityIndicator: UIViewController {
    let activityIndicator = UIActivityIndicator(style: .large)

    func showLoading() {
        activityIndicatorView.startAnimating()
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
            self?.activityIndicatorView.stopAnimating()
        }
    }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class ScreenWithButton: UIViewController {
    var counter = 0
    override func viewDidLoad() {
        super.viewDidLoad()

        let tapAction = UIAction { _ in
            self.counter += 1
        }

        let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 40), primaryAction: tapAction)
        view.addSubview(button)
    }
}

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:

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import RxSwift
import RxCocoa

class ScreenWithButton: UIViewController {
    var isLoggedIn: Bool = false
    var counter: Int = 0
    let button = UIButton()

    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        button.rx.tap
            .filter { [unowned self] in 
                self.isLoggedIn
            }.subscribe(onNext: [unowned self] {
                self.counter += 1
            }).disposed(by: disposeBag)
    }
}

or with Combine:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import Combine

class ScreenViewModel {
    let buttonTapped = PassthroughSubject<Void, Never>()
    private var isLoggedIn: Bool = false
    private var counter: Int = 0

    private var disposables = Set<AnyCancellable>()

    init() {
        buttonTapped.filter { [unowned self] in
            self.isLoggedIn
        }.sink { [unowned self] in
            self.counter += 1
        }.store(in: &disposables)
    }
}

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:

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:

When talking about closures:

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!