Select Page

In the previous installment of this series, you learned about Automatic Reference Counting and how it helps keep memory management in check. Remember that a class instance is deallocated if no properties, constants, or variables hold a strong reference to the class instance. I didn’t explain what a strong reference is, though.

What Is a Strong Reference

A reference to a class instance is strong by default. This is an example from the previous article.

class Device {

    let model: String
    let manufacturer: String

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

var deviceJim: Device?
var deviceJane: Device?

deviceJim = Device(model: "iPhone 7", manufacturer: "Apple")

The deviceJim variable holds a strong reference to the Device instance. Strong simply means that the class instance the reference points to cannot be deallocated as long as the reference exists.

A strong reference prevents a class instance from being deallocated.

Make sure you understand what a strong reference is. You need to understand this concept to grasp what reference cycles are and how you can avoid them.

Reference Cycles

A strong reference prevents the class instance it points to from being deallocated. This is easy to understand. But the use of strong references also introduces a risk. Do you see how strong references can cause problems? Let me show you an example.

The Delegation Pattern

If you’re reading this, then I bet you’re already familiar with the delegation pattern. The delegation pattern is simple but powerful. Apple’s frameworks make heavy use of delegation.

Table views are a fine example of the delegation pattern. A table view isn’t responsible for handling user interaction. It delegates this responsibility to a delegate object, an object that conforms to the UITableViewDelegate protocol. A table view notifies its delegate when the user taps a row in the table view. The result is that table views are highly reusable.

A table view is usually owned by a view controller. This means that the view controller keeps a strong reference to the table view. It’s also possible that the table view is owned by its superview, but let’s assume the view controller owns the table view. The table view isn’t deallocated as long as the view controller is alive because the view controller holds a strong reference to the table view.

The table view also keeps a reference to its delegate. In most scenarios, the view controller is the table view’s delegate. If the table view’s reference to the view controller, the delegate, is strong, then the view controller won’t be deallocated for as long as the table view is alive.

Wait a minute. The table view isn’t deallocated for as long as the view controller is around. Do you see the problem? Take a look at this diagram to better understand the problem.

A Strong Reference Cycle
A Strong Reference Cycle

This is known as a strong reference cycle. The view controller and the table view keep each other alive. Even if the view controller and its table view are no longer needed by the application, they are not deallocated. They continue to take up memory and the application is leaking memory. We’ve created a memory leak.

Dependencies

Strong reference cycles can also be caused by objects that depend on one another. Let me show you another example. If you sign up for a music service, you create an account and you choose a plan. We define an Account class and a Plan class to represent this in code.

class Account {

    // MARK: - Properties

    var plan: Plan

    ...

}

class Plan {

    // MARK: - Properties

    var account: Account

    // MARK: - Initialization

    init(account: Account) {
        self.account = account
    }
    
}

Because references are strong by default, an Account instance holds a strong reference to a Plan instance. The same is true for a Plan instance. A Plan instance holds a strong reference to an Account instance. This means we end up with another strong reference cycle.

We end up with another strong reference cycle.
We end up with another strong reference cycle.

Leaky Closures

Another common source of reference cycles or retain cycles is through the use of closures. Closures are a powerful tool in your toolbox, but you need to be careful in some scenarios. Like classes, closures are reference types. Because a closure can capture values from its surrounding scope, a closure can introduce a reference cycle. Let me show you how this happens by expanding the example we created earlier.

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
    }
    
}

I’ve expanded the Device class by defining a property, summary, of type () -> String, a closure. The default value of the summary property is a closure that returns a string containing the model and manufacturer of the device. The lazy keyword is required in this example since we need access to the model and manufacturer properties through self, the Device instance.

Do you see the problem? The Device instance holds a reference to the closure stored in the summary property. But the closure stored in the summary property also holds a reference to the Device instance, self, causing a strong reference cycle.

The closure strongly captures the Device instance.
The closure strongly captures the Device instance.

You can verify this by adding a deinitializer to the Device class, creating a Device instance, and setting the variable to nil.

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")
    }
    
}

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

device?.summary()

device = nil

The print statement of the deinitializer is not printed to the results panel on the right since the Device instance isn’t deallocated when device is set to nil.

Resolving Strong Reference Cycles

In the remainder of this series, we explore solutions that resolve the strong reference cycles we created in this installment of Understanding Swift Memory Management. Don’t worry, though, the solutions are not difficult to implement.

Fixing a memory leak isn’t difficult, but they are usually hard to find. A memory leak is easy to overlook and that’s often the problem. It’s easy to miss a strong reference cycle if you’re not paying attention.

Questions? Leave them in the comments below or reach out to me on Twitter.

<< What Is Automatic Reference Counting (ARC)How to Break a Strong Reference Cycle >>