Strong reference cycles negatively impact your application's performance. They lead to memory leaks and unexpected behavior that is often hard to debug. In this episode, I show you how to resolve the strong reference cycles we created in the previous episode of this series.
Turning Strong References Into Weak References
If you paid attention in the previous episode, then you may already know how to break a strong reference cycle. The solution is straightforward. We can break a strong reference cycle by using references that are not strong. That makes sense. Right?
This brings up the question "What do we replace the strong references with?" In the previous episode, we discussed the relationship between a view controller and its table view. The view controller still needs a reference to its table view and the table view needs a reference to its delegate, the view controller in the example. How do we resolve this problem?
By replacing a strong reference with a weak reference, the relationship between the objects remains intact and the strong reference cycle is broken. That is the solution we need.
What Is a Weak Reference?
A weak reference is similar to a strong reference. It too points to a class instance. The difference is that a weak reference doesn't prevent a class instance from being deallocated. This means that a weak reference can be used to break a strong reference cycle. Take a look at this diagram to better understand the solution.
Remember from the previous episode, a view controller holds a strong reference to its table view. If a table view also holds a strong reference to its delegate, the view controller in the example, we end up with a strong reference cycle. If the table view keeps a weak reference to its delegate, then the strong reference cycle is broken.
If the view controller is deallocated, then the table view it references is also deallocated. Because the table view keeps a weak reference to its delegate, the view controller in the example, the table view doesn't prevent the view controller from being deallocated. The memory leak we created in the previous episode is resolved by replacing a strong reference with a weak reference.
The UIKit framework uses this pattern for the UITableView
class. We can verify this by inspecting its interface. Press Command and click the UITableView
symbol in ViewController.swift. This takes us to the interface of the UITableView
class.
@available(iOS 2.0, *)
open class UITableView : UIScrollView, NSCoding, UIDataSourceTranslating {
public init(frame: CGRect, style: UITableView.Style) // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
public init?(coder: NSCoder)
open var style: UITableView.Style { get }
weak open var dataSource: UITableViewDataSource?
weak open var delegate: UITableViewDelegate?
...
}
The delegate
property of the UITableView
class is a variable property of type UITableViewDelegate?
. The weak
keyword prefixing the property declaration indicates that the property holds a weak reference to the class instance it points to. The same is true for the dataSource
property of the UITableView
class.
How to Use a Weak Reference?
The interface of the UITableView
class illustrates how to use a weak reference. The delegate
and dataSource
properties are declared weakly by prefixing the property declaration with the weak
keyword. There are a few subtle details you need to be aware of, though.
When the class instance the property or variable points to is deallocated, the weak reference no longer points to a class instance. Unlike a strong reference, it cannot prevent the class instance from being deallocated. In other words, it is possible that a weak property or a weak variable doesn't point to a class instance. A weak reference is not guaranteed to always have a value.
A weak reference is not guaranteed to always have a value.
For that reason, a weak reference is always of an optional type. If the class instance the weak reference points to is deallocated, then the weak reference is automatically set to nil
. This is a safety mechanism to prevent access to a class instance that may no longer exist.
There is another requirement you need to be aware of. Because Automatic Reference Counting needs to be able to set a weak reference to nil
when the class instance it points to is deallocated, a weak reference is always declared as a variable.
The declaration of the UITableView
class confirms this. The delegate
property is a variable property and it is of type UITableViewDelegate?
.
@available(iOS 2.0, *)
open class UITableView : UIScrollView, NSCoding, UIDataSourceTranslating {
public init(frame: CGRect, style: UITableView.Style) // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
public init?(coder: NSCoder)
open var style: UITableView.Style { get }
weak open var dataSource: UITableViewDataSource?
weak open var delegate: UITableViewDelegate?
...
}
Breaking a Strong Reference Cycle
In the previous episode, we discussed the relationship between accounts and plans. An account has a reference to a plan and a plan has a reference to an account. The account and the plan depend on one another.
Let's translate that example to code. The Account
class defines a variable property, plan
. The plan
property is an implicitly unwrapped optional. The Plan
class defines a constant property, account
, of type Account
. It also defines an initializer that accepts an Account
instance.
class Account {
// MARK: - Properties
var plan: Plan!
}
class Plan {
// MARK: - Properties
let account: Account
// MARK: - Initialization
init(account: Account) {
self.account = account
}
}
To break the strong reference cycle between an Account
instance and a Plan
instance, we declare the account
property of the Plan
class as weak. Remember that this has a few consequences. We need to declare the account
property as a variable property and it needs to be of an optional type.
class Account {
// MARK: - Properties
var plan: Plan!
}
class Plan {
// MARK: - Properties
weak var account: Account?
// MARK: - Initialization
init(account: Account) {
self.account = account
}
}
Defining the account
property as an optional is fine, but it is inconvenient. We know that every Plan
instance is guaranteed to have an Account
instance associated with it. We can work around this inconvenience by replacing the weak keyword with the unowned keyword. This turns the weak reference into an unowned reference. We discuss unowned references in detail in the next episode.
class Account {
// MARK: - Properties
var plan: Plan!
}
class Plan {
// MARK: - Properties
unowned var account: Account
// MARK: - Initialization
init(account: Account) {
self.account = account
}
}
Once a plan is associated with an account, the account the plan references shouldn't change. This is easy to implement by declaring the setter of the account
property as private. This is a subtle but important detail.
class Account {
// MARK: - Properties
var plan: Plan!
}
class Plan {
// MARK: - Properties
private(set) unowned var account: Account
// MARK: - Initialization
init(account: Account) {
self.account = account
}
}
Mind the Exclamation Mark
The previous example comes with a warning. The Account
class declares the plan
property as an implicitly unwrapped optional. In Swift, an exclamation mark communicates potential danger. I have declared the plan
property as an implicitly unwrapped optional for convenience, but it isn't something I recommend. I go into detail about this topic in When Should You Use Implicitly Unwrapped Optionals.
What Is the Difference Between Weak and Unowned References
Unowned references also come with a warning. An unowned reference is very similar to a weak reference. It weakly references the class instance it points to and, like a weak reference, it can be equal to nil
. If an object accesses an unowned reference that is equal to nil
, then a fatal error is thrown and your application crashes. This is similar to accessing an implicitly unwrapped optional that is equal to nil
.
In the next episode of Understanding Swift Memory Management, we take a closer look at the differences between weak and unowned references. The differences are subtle but profound.