How to Use a Capture List to Break a Retain Cycle

Understanding Swift Memory Management

If you've been paying attention, then you may have noticed that we haven't resolved the strong reference cycle of the Device class. Remember from earlier in this series, this is what the Device class looks like.

class Device {

    let model: String
    let manufacturer: String

    lazy var summary: () -> String = {
        return "\(self.model) (\(self.manufacturer))"
    }

    init(model: String, manufacturer: String) {
        self.model = model
        self.manufacturer = manufacturer
    }

    deinit {
        print("Deallocated Device")
    }

}

We created an instance of the Device class, assigned it to a variable, and set the variable to nil. Setting device to nil doesn't result in the deallocation of the Device instance.

var device: Device? = Device(model: "iPhone 7", manufacturer: "Apple")

device?.summary()

device = nil

Remember that the summary property is of type () -> String, a closure. The default value assigned to the summary property references the Device instance through the self keyword. Because closures are reference types, this creates a strong reference cycle. In other words, the closure strongly references the class instance the property belongs to and the Device instance strongly references the closure.

Because closures are reference types, this creates a strong reference cycle.

Capturing Values

Closures are powerful and versatile, which is in part due to their ability to capture values from the scope in which they are defined. If you'd like to learn more about closures and capturing values, I recommend reading the chapter on closures in The Swift Programming Language.

When a closure captures a value from its surrounding context, Swift is responsible for managing the memory for these values. But as I mentioned earlier, there are times you need to give Automatic Reference Counting a hand. Let's revisit the summary property of the Device class.

class Device {

    let model: String
    let manufacturer: String

    lazy var summary: () -> String = {
        return "\(self.model) (\(self.manufacturer))"
    }

    ...

}

The default value we assign to the summary property captures self, the Device instance. The closure keeps a strong reference to the Device instance. The Device instance also keeps a strong reference to the closure.

Another Strong Reference Cycle

If you've read the previous articles of this series, then you probably know how we can break this strong reference cycle. We need to make sure the closure doesn't strongly reference self, the Device instance. But how can we do that?

Defining a Capture List

The references a closure holds to reference types are strong by default. We can change this behavior by defining a capture list. The capture list of a closure determines how the reference types captured by the closure are managed in memory. In other words, we can define a capture list to tweak the rules. The solution is simple as you can see below.

class Device {

    let model: String
    let manufacturer: String

    lazy var summary: () -> String = { [unowned self] in
        return "\(self.model) (\(self.manufacturer))"
    }

    ...

}

The capture list is positioned before the parameters of the closure or, if no parameters are defined, before the in keyword. A capture list is nothing more than a collection of pairs wrapped in square brackets.

  • the type of reference (weak or unowned) on the left
  • and the reference it applies to on the right

You can use commas to separate multiple pairs. You already know and understand the unowned keyword. By capturing self as an unowned reference, the strong reference cycle is broken.

The strong reference cycle is broken.

Another Example

Let's take a look at a few more examples. Reactive programming in Swift heavily relies on closures. To avoid memory leaks caused by strong reference cycles, you need to make sure you understand capture lists and when to use them. In this example, we subscribe to themeSubject and listen for next events. When a next event occurs, we update the tintColor property of the window.

applicationSettingsManager.themeSubject
    .subscribe(onNext: { [unowned self] in
        self.window?.tintColor = $0.tint
    })
    .addDisposableTo(disposeBag)

In this example, self refers to the application delegate. It's the application delegate that owns the application settings manager.

Don't Skip Memory Management

Even though memory management may seem a more advanced topic, it isn't. It is essential that you understand how Automatic Reference Counting works, what the differences are between value types and reference types, and when to use a weak or unowned reference instead of a strong reference.

Stop Writing Swift That Sucks

Download the Swift Patterns I Swear By

About Bart Jacobs

About bart jacobs

My name is Bart Jacobs and I run a mobile development company, Code Foundry. I've been programming for more than fifteen years, focusing on Cocoa development soon after the introduction of the iPhone in 2007.

Stop Writing Swift That Sucks

In my free book, you learn the four patterns I use in every Swift project I work on. You learn how easy it is to integrate these patterns in any Swift project.