Capture Lists
Posted by scotteg on April 15, 2020
Contents
- Introduction
- Value and Reference Types
- Strong, Weak, and Unowned References
- Strong Reference Cycles
- Preventing Strong Reference Cycles
- Escaping vs. Nonescaping Closures
- Defining Capture Lists
- Preventing Strong Reference Cycles in Closures
- Capturing Multiple Class References
- Capture List Decision Flowchart
- Conclusion
Introduction
Capture lists have been available in Swift since its debut at WWDC 2014. They play a crucial role in ensuring your asynchronous code does not leak memory or cause exceptions at runtime. In order to use capture lists correctly, you’ll need a good grasp of related topics including value vs. reference types, strong reference cycles, and escaping vs. nonescaping closures. In this article, I’ll cover these supporting topics first, and then explain why, when, and how to use capture lists effectively.
Value and Reference Types
Value types include all of the basic data types in the Swift Standard Library, such as String
and Int
. Collections such as Array
are also value types. These types are defined as structures. In other words, structures are value types. Enumerations are also value types.
Value types are passed by copy¹. This means when you assign a value type to another variable or constant, or pass a value type to a function, the value is copied and the original value is left untouched.
Example of value types
var a = 1
let b = a
a = 2
print(a, b) // Prints 2, 1
Reference type include classes, functions, and closures².
Reference types are passed by reference. When you assign a reference to a variable or constant, or pass a reference type to a function, what you are actually passing is a pointer to the same instance of that reference type in memory.
Example: Reference types
class SomeClass {
var value: String
init(_ value: String) {
self.value = value
}
}
let a = SomeClass("Hello, world!")
let b = a
a.value = "Changed"
print(a.value, b.value) // Prints Changed Changed
Strong, Weak, and Unowned References
Note: This section introduces some preliminary terminology and theory. These topics will be expanded upon and used in examples throughout the rest of the article.*
A strong reference will refuse to release or “let go” of the instance it is referring to. When you define a property of a reference type, it will be a strong reference by default. A strong reference property, once assigned, will not release the instance it refers to until either that property is set to nil
³ or the property’s owner is deallocated.
Within the definition of a class, the keyword self
is a strong reference to the instance of that class.
Only class reference types may be marked as weak
or unowned
. In other words, a closure property cannot be marked weak
or unowned
.
A weak reference will only hold on to the class instance it is referring to until there are no longer any strong references to that instance. Once there are no longer any strong references, a weak reference will be set to nil
and release its hold so that the instance it referred to can be deallocated and its memory freed. For this reason, weak properties must be defined as Optional
s.
Similar to a weak reference, an unowned reference does not keep a strong hold on the class instance it refers to. However, an unowned reference expects that the instance it refers to will always have a value, and will not be deallocated before the parent of the unowned instance. Therefore, unowned properties should not be defined as an Optional
.
Strong Reference Cycles
A strong reference cycle occurs when two class reference type instances hold a strong reference to each other. In this scenario, each instance will refuse to let go of the other instance. The result is, neither instance is deallocated and their memory is leaked.
Example: Strong reference cycle
// 1
class Band {
var singer: Singer?
init(singer: Singer? = nil) {
self.singer = singer
}
deinit {
print("\(Self.self)", #function)
}
}
class Singer {
let name: String
let band: Band
init(name: String, band: Band) {
self.name = name
self.band = band
}
deinit {
print("\(Self.self)", #function)
}
}
// 2
var steelDragon: Band! = Band()
var bobby: Singer! = Singer(name: "Bobby Beers", band: steelDragon)
// 3
steelDragon.singer = bobby
// 4
bobby = nil
steelDragon = nil
In the above code:
Band
andSinger
classes are defined. Each class has a property of the type of the other class. And each class will print a descriptive message in theirdeinit
method.- Instances of both classes are created.
- The
Singer
instance is assigned to theBand
instance’ssinger
property. At this point a strong reference cycle is created, because each instance holds a strong reference to the other instance. - Each instance is set to
nil
, but neither instance is deallocated, because it still has a strong reference to it.
Nothing will be printed because neither deinit
is called.
Preventing Strong Reference Cycles
To prevent a strong reference cycle, at least one of the properties between two class instances that reference each other must either be marked as weak
or unowned
.
Remember:
1. A weak property must be of anOptional
type, because it will be set tonil
when there are no other strong references to that instance.
2. An unowned property should not be anOptional
, because it is expected to never benil
for the life of the instance that owns that property.
Example: Breaking a strong reference cycle with a weak reference
// 1
class Band {
weak var singer: Singer?
init(singer: Singer? = nil) {
self.singer = singer
}
deinit {
print("\(Self.self)", #function)
}
}
// 2
class Singer { ... }
var steelDragon: Band! = Band()
var bobby: Singer! = Singer(name: "Bobby Beers", band: steelDragon)
steelDragon.singer = bobby
// 3
bobby = nil
steelDragon = nil
In the above code:
- Replaces previous definition of
Band
. - Same definition of
Singer
as in previous example. - The weak property will break the strong reference cycle and both instances will be deallocated.
This will print:
Singer deinit
Band deinit
Example: Breaking a strong reference cycle with an unowned reference
// 1
class Band {
var singer: Singer?
init(singer: Singer? = nil) {
self.singer = singer
}
deinit {
print("\(Self.self)", #function)
}
}
// 2
class Singer {
let name: String
unowned let band: Band
init(name: String, band: Band) {
self.name = name
self.band = band
}
deinit {
print("\(Self.self)", #function)
}
}
var steelDragon: Band! = Band()
var bobby: Singer! = Singer(name: "Bobby Beers", band: steelDragon)
steelDragon.singer = bobby
// 3
bobby = nil
steelDragon = nil
In the above code:
- Replaces previous definition of
Band
. In this version, thesinger
property is a regular (strong) reference to anOptional
. - Replaces the definition of
Singer
. In this version, theband
property is an unowned reference. - The unowned property will break the strong reference cycle and both instances will be deallocated.
This will print:
Singer deinit
Band deinit
Escaping vs. Nonescaping Closures
When a closure is passed as a parameter to a function, that closure can be defined to behave in one of two ways:
- Nonescaping - program flow is not allowed to exit the scope of the function until the closure parameter has finished execution. This is synchronous behavior.
- Escaping - program flow will exit the scope of the function before the closure parameter has finished execution. This is asynchronous behavior.
A closure parameter is nonescaping by default. In order to define a closure parameter as escaping, mark the parameter using the @escaping
attribute.
Example: Defining an escaping closure
func execute(action: @escaping () -> Void) {
action()
}
When an escaping closure parameter of a method⁴ references any member of that type, including properties and methods, self
must be explicitly written. This is enforced by the compiler to make the capture semantics explicit. In other words, the compiler is reminding you of your responsibility to properly handle captures of self
or any of its members.
Defining Capture Lists
Capture lists are primarily used to prevent strong reference cycles in closures.
A capture list is defined at the beginning of the body of a closure, before argument names and the return type are defined. As implied by its name, a capture list can contain more than one value. Each capture is defined following this format:
weak/unowned captureName = classReferenceTypeInstance
If the capture name is self
, or it matches a property name of the class in which it is defined, you can omit explicitly defining the assignment and it will be inferred.
Example: Defining a capture list
class SomeClass {
let value = "Hello, world!"
lazy var printValue: () -> Void = { [unowned self] in
print(self.value)
}
}
In the above code, an unowned capture of self
is created. Because the capture name is self
, it is not necessary to explicitly assign it (e.g., [unowned self = self]
).
Tip: A lazy closure property is treated as an escaping.
Multiple captures are separated by commas.
Example: Defining a capture list with multiple captures
class SomeClass {
// 1
typealias PrintableObject = AnyObject & CustomStringConvertible
var value: PrintableObject?
lazy var printValue: () -> Void = {
// 2
[unowned self, weak theValue = self.value] in
guard let theValue = theValue else { return }
print(theValue)
}
init(value: PrintableObject) {
self.value = value
}
func execute(action: @escaping () -> Void) {
action()
}
}
In the above code:
- A type alias is defined to represent class types that conform to the
CustomStringConvertible
protocol by providing a textual representation. - The capture list defines an unowned capture of
self
and weak capture ofself.value
, using explicit assignment for the second capture because it uses a different capture name.
Preventing Strong Reference Cycles in Closures
Remember:
1. A closure is a reference type.
2. Theself
keyword used in a class definition is a strong reference to the instance of that class.
For this section, an iOS app project will be referenced. The app displays a Present Alert button, and tapping it presents an alert. In the alert’s definition, an action is performed asynchronously after a three-second delay. The action is an escaping closure parameter of the DispatchQueue.asyncAfter
method. To test for the occurrence of a strong reference cycle, dismiss the alert within three seconds.
If you’d like to follow along with the examples in this section, you can download a starter version of this project here.
In each of the following examples, the following actions are presumed:
- The alert view controller will be presented.
- The alert view controller will be dismissed before three seconds have elapsed.
Example: No strong reference cycle, closure is executed
class AlertViewController: UIAlertController {
var value = "Hello, world!"
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
print(self.value)
}
}
deinit {
print(#function)
}
}
In the above code, the print
statement will be executed after three seconds, even if the alert is dismissed beforehand. The trailing closure of asyncAfter
holds a strong reference to self
until the print
statement has executed. At that point, self
is released and the alert instance is deallocated. This will print:
Hello, world!
deinit
Example: No strong reference cycle, closure is not executed, exception because self is nil
class AlertViewController: UIAlertController {
var value = "Hello, world!"
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [unowned self] in
print(self.value)
}
}
deinit {
print(#function)
}
}
In the above code, self
will be nil
before the three-second delay has elapsed. However, creating an unowned capture of self
requires that self
will never be nil
. So, when the print
statement is executed and it attempts to reference self.value
, an exception will occur.
There are three situations where creating an unowned capture of self
is advisable:
- A view controller that will never be dismissed.
- The class is implemented as a singleton, i.e., only a single instance of the class will be initialized and shared globally, and the instance will not be deallocated during the app’s life-cycle.
self
is referenced in a nonescaping closure.
Tip: Presented view controllers that can be dismissed and classes that can be deallocated should not contain unowned captures.
Example: No strong reference cycle, closure is not executed
class AlertViewController: UIAlertController {
var value = "Hello, world!"
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
guard let self = self else { return }
print(self.value)
}
}
deinit {
print(#function)
}
}
In the above code, a weak capture of self
is made inside the closure, and the guard
statement attempts to create a local temporary strong reference to self
— i.e., self
is not nil
— before executing the print
statement. If the alert is dismissed before the three-second delay elapses, self
will be nil
and the print
statement will not be executed. This will print:
deinit
Tip: It is also common to use a different name for the strong reference to
self
in a closure, e.g.,guard let strongSelf = self else { return }
.
Example: Strong reference cycle, closure is executed
class AlertViewController: UIAlertController {
var value = "Hello, world!"
var action: (() -> Void)?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
action = {
print(self.value)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.action!()
}
}
deinit {
print(#function)
}
}
In the above code, an action
property is added that is a closure. self
holds a strong reference to its action
property, and the value assigned to it holds a strong reference to self
. This creates a strong reference cycle. Neither the closure or the instance of the alert will be deallocated, and their memory is leaked. This will print:
Hello, world!
Example: No strong reference cycle, closure is executed
class AlertViewController: UIAlertController {
var value = "Hello, world!"
var action: (() -> Void)?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
action = { [weak self] in
guard let self = self else { return }
print(self.value)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.action!()
}
}
deinit {
print(#function)
}
}
In the above code, the action
closure property is assigned a value that first creates a weak capture of self
, and then attempts to create a temporary strong reference to self
. If it’s successful, it executes the print
statement that references self
. The weak capture prevents a strong reference cycle. However, the asyncAfter
closure will hold a strong reference to self
until it has finished executing. This will print:
Hello, world!
deinit
This may be desired behavior in some situations, where you want the closure to keep a strong reference to self
until it can finish its work. However if the work performed in the closure is unnecessary, and especially if that work is expensive, it would be more performant to avoid executing that closure if self
is nil
. You’ll see how to do this in the last example.
Example: Strong reference cycle, closure is executed
class AlertViewController: UIAlertController {
var value = "Hello, world!"
var action: (() -> Void)?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
action = {
print(self.value)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
guard let self = self else { return }
self.action!()
}
}
deinit {
print(#function)
}
}
In the above code, it might seem at first that the weak capture of self
in the closure will prevent a strong reference cycle. However, even though the asyncAfter
closure only weakly captures self
, the action
property of self
strongly captures self
. Therefore, self
strongly holds action
, and action
strongly holds self
. This creates a strong reference cycle. This will print:
Hello, world!
Example: No strong reference cycle, closure is not executed
class AlertViewController: UIAlertController {
var value = "Hello, world!"
var action: (() -> Void)?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
action = { [weak self] in
guard let self = self else { return }
print(self.value)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
guard let self = self else { return }
self.action!()
}
}
deinit {
print(#function)
}
}
In the above code, both the action
closure and the asyncAfter
closure create weak captures of self
and then temporary strong references of self
before referencing self
’s members. This will print:
deinit
This example demonstrates preventing a strong reference cycle, and it prevents executing the asyncAfter
closure if self
is nil
, i.e., the alert was already dismissed and is deallocated. This would be advisable if the result of executing that closure is not needed.
Tip: This also applies to nonescaping closures. If the only remaining strong reference to
self
is in a nonescaping closure, creating a weak capture and then using aguard
statement at the top to attempt to create a temporary strong reference will fail and the closure will not be executed.
Capturing Multiple Class References
Creating capture lists of self
is by far the most common technique to prevent strong reference cycles. However, it is also possible to create capture lists that capture other class references beside self
, or that capture more than one class reference.
Example: Capturing multiple class references
class AlertViewController: UIAlertController {
// 1
class Action {
let execute: () -> Void
init(action: @escaping () -> Void) {
self.execute = action
}
}
var value = "Hello, world!"
var action1: Action?
var action2: Action?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 2
action1 = Action() { [weak self] in
self?.value = "Changed"
}
action2 = Action() { [weak self] in
guard let self = self else { return }
print(self.value)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// 3
[weak action1, weak action2] in
guard let action1 = action1,
let action2 = action2
else { return }
action1.execute()
action2.execute()
}
}
deinit {
print(#function)
}
}
In the above code:
- An
Action
class is defined to encapsulate a closure property. - Both the
action1
andaction2
properties are assigned instances ofAction
that weakly captureself
. - A capture list is created that create weak captures of
action1
andaction2
, and then strong references to those class instances before calling theirexecute
method.
This will print:
deinit
Capture List Decision Flowchart
Use this flowchart to determine if a capture list should be defined in a closure in a class type.
Conclusion
In this article, you learned about:
- Value vs. reference types.
- Strong, weak, and unowned references.
- Strong reference cycles between class instances and how to prevent them by creating weak and unowned properties.
- Escaping vs. Nonescaping closures.
- Capture lists and how to define them with one or more captures.
- Using capture lists in escaping closures to prevent strong reference cycles.
You can download the final version of the project in this article here.
¹ Technically, value types are passed by copy-on-write, which means they won’t actually be copied until they need to be mutated.⏎
² Functions are actually just named closures.⏎
³ This would require the property to be an Optional
.⏎
⁴ A function within a type definition is referred to as a method.⏎
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
© 2020 Scott Gardner