Weak and Unowned keywords in Swift

Why it is important to use them in closures — an example

Kristiina
Kristiina
Sep 16 · 7 min read

If you’re familiar with Swift you should have heard the keywords unowned and weak. If you haven’t heard of them, look them up now. I won’t explain everything about the keywords, but I will tell you why you should know and use them.

What happens if we do not use unowned or weak keywords in closures?
The short answer is: memory leaks!
The long answer is: let me show you…


Let’s See How It Works!

To demonstrate how not using unowned or weak keywords can create memory leaks, I wrote a simple app that lets you enter web request status codes, then displays them in a tableview with corresponding cat pictures.

I did not come up with the amazing idea to pair status codes and cat pictures but did discover this great, easy to use API that does:

It seemed like a fun API to use in an example app.


App Setup

The setup is pretty easy. When opening the app you are first presented with a screen with a “Start” button. On pressing the button the main view appears. In the main view you can enter a three-digit status code and press “Find”. All the entered status codes are displayed in the tableview below. If an image can be queried from http.cat/[status code] then it is displayed next to the corresponding status code.


Data Model

The data model is fairly simple. We have one class — CatStatus. There are variables for value (the status code value), imageData, and a function called updated, which is called when the API request finishes and imageData has been updated.

import Foundation

class CatStatus {
let value: String
var imageData: Data?
var updated: (()->())?

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

func makeRequest() {
DispatchQueue.global().async {
if let url = URL(string: "https://http.cat/\(self.value)") {
if let data = try? Data(contentsOf: url) {
self.imageData = data

if let updated = self.updated {
updated()
}
}
}
}
}
}

The function makeRequest() makes a request to the http.cat API to query the cat image for the given status code.


View Controller

When the user has clicked on the “Find” button the following function is executed in the view controller:

@IBAction func didSubmit() {
if let text = self.textField?.text {
if text.count == 3 {
self.textField?.textColor = .black // status code is valid
self.textField?.text = nil

let newCat = CatStatus(value: text)
self.cats.append(newCat)

let indexPath = IndexPath(row: cats.count - 1, section: 0)
tableView?.insertRows(at: [indexPath], with: .automatic)

newCat.updated = { [unowned self] in
DispatchQueue.main.async {
self.tableView?.reloadRows(at: [indexPath], with: .automatic)
}
}
newCat.makeRequest()
} else {
self.textField?.textColor = .red // status code has more or less than 3 digits
}
if text.count == 0 {
self.textField?.textColor = .black // status code is empty
}
}
}

We first check whether the text is not nil and the text length is 3 — this means we might have a valid status code and should add a new row to the tableview. We create a new CatStatus instance and add it to our cats list. Then we tell the tableview that a new row should be inserted.

Now comes the interesting part: we want to tell the new CatStatus to make an API request. We first set the updated variable so that we are notified when imageData is updated. If imageData has been updated we want the tableview to reload the corresponding row.

We then start the request.


Unowned and Weak Keywords

This is the interesting part. As you can see [unowned self] is currently commented out. But what is the difference when we write [unowned self] or not? Let’s run the app and you’ll see. I will then explain what the keyword does.

newCat.updated = { // [unowned self] in
DispatchQueue.main.async {
self.tableView?.reloadRows(at: [indexPath], with: .automatic)
}
}

Experiment

I ran the application twice. First with [unowned self] commented out and then not. Nothing else was changed in the code. You’ll see the difference in memory usage.

The application run through consists of the following steps:

  1. Open application
  2. Click on “Start”
  3. Fill in “100” as status code, click on “Find”
  4. Fill in “200” as status code, click on “Find”
  5. Fill in “300” as status code, click on “Find”
  6. Fill in “400” as status code, click on “Find”
  7. Click on “Back”
  8. Repeat steps 2.-7. again 3 more times

First run-through

The first run-through where [unowned self] is commented out gave us the following result in memory usage:

As you can see, memory usage grows steadily. Each time an image is queried more memory is being used. It’s important to note that not all images are displayed at the same time. When we go back to the first screen the data from the previous queries is forgotten, but not released.

Second run-through

The second run-through with [unowned self] not commented out:

As you can see every time we go back to the main screen the memory that was allocated for the queried images is freed.


Explanation

Why isn’t memory freed in the first run through?

newCat.updated = { // [unowned self] in
DispatchQueue.main.async {
self.tableView?.reloadRows(at: [indexPath], with: .automatic)
}
}

Swifts magic with automatic memory management:

  • every time we create a strong reference, reference count increases by one
  • every time we free a strong reference, reference count decreases by one

This way swift knows if an object is still referenced by another object and should be kept alive. If the reference count is zero, then the object is deallocated.

What happens in such a closure is that since we reference self, we create a new strong reference, thus the reference count is increased by one. Now when we return to the first view what should happen is:

  • View controller instance is deallocated, as we don’t reference it anymore
  • All CatStatus instances are deallocated, as we don’t reference them anymore
  • All imageData variables are deallocated, as we don’t reference them anymore

This does not happen, since this closure still holds a reference to the ViewController instance (here: self). This means that the ViewController is not deallocated. Therefore there are still references to the CatStatus instances and hence imageData variables.

The result is a lot of memory in use that we cannot access.

The solution: use either [unowned self] or [weak self] in the closure. This way we can still reference self, but no strong reference is created.


Unowned or Weak?

When we’re not using a strong reference it’s possible that self can have been already deallocated when the closure is called.

When we use unowned and self is already deallocated`, the application crashes. Therefore it is important to only use unowned if you are 100% sure that this cannot happen.

When we use weak and self is already deallocated, then self will be nil. This means that when using weak, self will be an optional. This gives us the possibility to handle the case when self has been deallocated.

In most cases, it makes sense to use weak, as it is safer. I used unowned in this example as it meant I needed to alter less code.


Conclusion

Although swift makes memory management a lot easier than it was in the earlier days of Objective-C, it’s still important to look into it and to understand how reference counting works.

I used a limited and simple app to show how memory leaks can happen, in this case, image data that was not freed increased used memory very fast. In real-world applications where an application can run for a long time, even smaller leaks can create problems over a longer time frame.

Take this as a motivation to learn about unowned and weak if you haven’t already.


Better Programming

Advice for programmers.

Kristiina

Written by

Kristiina

I’m an iOS developer with an interest in app security. I’m currently also employed as a junior researcher working on static analysis for mobile applications.

Better Programming

Advice for programmers.