7.
Memory Management
Written by Matt Galloway
In “Swift Apprentice: Fundamentals - Chapter 15, Advanced Classes”, you explored elementary memory management when examining the class lifetime. You also learned about automatic reference counting (ARC). In most cases, Swift’s memory management works automatically with little to no effort from you.
However, certain relationships between objects sometimes present a problem the compiler can’t help you with. That’s where you come in.
In this chapter, you’ll revisit the concept of reference cycles and learn about resolving them. You’ll also learn to use capture lists in closures to capture values from the enclosing scope to resolve memory management problems. By the end of the chapter, you’ll have mastered the art of breaking reference cycles — but now it’s time to start by learning how they happen.
Reference cycles for classes
Two class instances with a strong reference to each other create a strong reference cycle. This situation can lead to a memory leak if the cycle is never broken. That’s because each instance keeps the other one alive, so their reference counts never reach zero. If no other object has a reference to either of the objects, this will likely result in a leak since there is no way to access them for deallocation, even though they may no longer be in use.
For example, our website has a mountain of top-notch programming tutorials, most of which an editor scrutinizes before you see them. Create a new playground and add the following code:
class Tutorial {
let title: String
var editor: Editor?
init(title: String) {
self.title = title
}
deinit {
print("Goodbye tutorial \(title)!")
}
}
This class models a tutorial. In addition to a title property, a tutorial might have an editor — or it might not. It’s optional. Recall that Swift automatically calls the deinitializer, deinit, and releases the object from memory when the reference count drops to zero.
Now that you’ve defined an editor for each tutorial, you need to declare an Editor class, like so:
class Editor {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye editor \(name)!")
}
}
Each editor has a name and a list of tutorials they have edited. The tutorials property is an array that you can add to.
Now, define a brand-new tutorial for publishing and an editor to ensure it meets our high standards:
do {
let tutorial = Tutorial(title: "Memory Management")
let editor = Editor(name: "Ray")
}
This code and subsequent examples use do {} to add a new scope. Any references created in a scope will be cleared at the end of the scope. In this case above, tutorial and editor will be cleared at the closing brace of the do {} scope. We expect both tutorial and editor to be deallocated because nothing is referencing them after these references are cleared.
Run the code above, and you’ll see the following in the console:
Goodbye editor Ray!
Goodbye tutorial Memory Management!
This output is what you might expect. If you’re wondering why it’s in that order, it’s because reference counts decrement in the reverse order of creation. Hence, the Editor reference decrements to zero first, and the Editor object deallocates since no more references exist. Then, the Tutorial reference decrements to zero, and the Tutorial object deallocates.
Note: You should be careful about relying on the exact ordering of deallocation as it is still an area of discussion and development. The direction is for it to become more stable and predictable as it works in the playground, but different versions of Swift may behave differently when compiler optimizations are enabled.
Now add the following code:
do {
let tutorial = Tutorial(title: "Memory Management")
let editor = Editor(name: "Ray")
tutorial.editor = editor
editor.tutorials.append(tutorial)
}
Although both references go out of scope and decrement, deinitializers aren’t called, and nothing prints to the console — bummer! You created a reference cycle between the tutorial and its corresponding editor. The runtime system never releases the objects from memory even though you don’t need them anymore.
Notice how the objects don’t deallocate, but there isn’t a way to access them since you no longer have any variable you can refer to after the do {} scope finishes. This situation is a memory leak.
Now that you understand how reference cycles happen, you can break them. Weak references to the rescue!
Weak references
Weak references are references that don’t play any role in the ownership of an object. The great thing about using them is that they automatically detect when the underlying object has disappeared. This automatic detection is why you always declare them with an optional type. They become nil once the reference count of the referenced object reaches zero.
A tutorial doesn’t always have an editor assigned, so it makes sense to model it as an optional type. Also, a tutorial doesn’t own the editor, so making it a weak reference makes perfect sense. Change the property’s declaration in the Tutorial class to the following:
weak var editor: Editor?
You break the reference cycle with the weak keyword. The console will now show that both deinitializers run and print their output to the console:
Goodbye editor Ray!
Goodbye tutorial Memory Management!
Note: You can’t define a weak reference as constant,
let, because it will change tonilduring runtime when the underlying object deallocates.
Unowned References
You have another means to break reference cycles: Unowned references. These behave like weak ones in that they don’t change the object’s reference count.
However, unlike weak references, they always expect to have a value — you can’t declare them as optionals. Think of it this way: A tutorial cannot exist without an author. Somebody has to write words for the editor to change. :] At the same time, a tutorial does not “own” the author so the reference could be unowned.
Change the code a little before looking at unowned references.
The tutorial doesn’t yet have an author. Modify its declaration as follows:
class Tutorial {
let title: String
let author: Author
weak var editor: Editor?
init(title: String, author: Author) {
self.title = title
self.author = author
}
deinit {
print("Goodbye tutorial \(title)!")
}
}
Add the following Author class as well:
class Author {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye author \(name)!")
}
}
Here, you guarantee a tutorial always has an author; Author is not declared as optional. On the other hand, tutorials is a variable that can change after initialization.
do {
let author = Author(name: "Alice")
let tutorial = Tutorial(title: "Memory Management",
author: author)
let editor = Editor(name: "Ray")
author.tutorials.append(tutorial)
tutorial.editor = editor
editor.tutorials.append(tutorial)
}
The output in the console will look like this:
Goodbye editor Ray!
The Editor is deallocated, but not the rest of the objects. You’re making another reference cycle — this time between the tutorial and its corresponding author. Each tutorial on the website has an author. There are no anonymous authors here! The tutorial’s author property works perfectly as an unowned reference since it’s never nil. Change the property’s declaration in the Tutorial class to the following:
class Tutorial {
unowned let author: Author
// original code
}
This code breaks the reference cycle with the unowned keyword. All the deinit methods run and print the following output to the console:
Goodbye editor Ray!
Goodbye author Alice!
Goodbye tutorial Memory management!
A word of caution is in order here: Be aware that using unowned comes with some danger. It’s the same danger you get from implicitly unwrapped optionals or using try!. That is, if the unowned property references an object that gets deallocated, then any access to that property will result in a crash of the program. So, use these only when you are sure the object will be alive.
Using a weak property is always safer, and all you have to do is safely unwrap the optional to account for the object potentially being nil. The reason to use unowned is when you are sure you want to trade the safety for the ease of not needing to unwrap an optional.
That’s it for reference cycles for classes. Now it’s time to look at reference cycles with closures.
Reference Cycles with Closures
In Chapter 8 of the Fundamentals book, “Collection Iteration With Closures”, you learned that closures capture values from the enclosing scope. Because Swift is a safe language, closures extend the lifetime of any object they use to guarantee those objects are alive and valid. This automatic safety is convenient, but the downside is that you can inadvertently create a reference cycle if you extend the lifetime of an object that captures the closure. Closures, you see, are reference types themselves.
For example, add a property that computes the tutorial’s description to the Tutorial class like this:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Remember that a lazy property isn’t assigned until its first use and that self is only available after initialization.
Now, print the tutorial’s description to the console. Add the following code right after the tutorial object’s declaration:
print(tutorial.description())
You’ve created another strong reference cycle between the tutorial object and the closure by capturing self! The Tutorial object holds on to the closure in description, which holds on to the Tutorial object through the reference to self. So, the Tutorial is no longer deallocated.
You’ll need to know about a language feature called capture lists to break the cycle.
Capture Lists
Capture lists are a language feature to help you control exactly how a closure extends the lifetime of instances it references. Capture lists are lists of variables captured by a closure. They appear at the beginning of the closure before any arguments.
First, consider the following code snippet with no capture list:
var counter = 0
var fooClosure = {
print(counter)
}
counter = 1
fooClosure()
The call of fooClosure() prints the counter variable’s updated value of 1 because it has a reference to the counter variable. Now, add a [c = counter] capture list:
counter = 0
fooClosure = { [c = counter] in
print(c)
}
counter = 1
fooClosure()
Most of the time, you don’t bother creating a new variable name like c. The shorthand [counter] capture list creates a counter local variable that shadows the original counter:
counter = 0
fooClosure = { [counter] in
print(counter)
}
counter = 1
fooClosure()
The call of fooClosure() prints 0 in this case because counter is a shadowed copy. The counter variable is copied when fooClosure is created and therefore remains 0 inside fooClosure. Setting counter to 1 does not affect the copy used when fooClosure is called.
Remember that “constant” has a different meaning for reference types when dealing with objects. A capture list will cause the closure to capture and store the current reference stored inside the captured variable with reference types. Changes made to the object through this reference will remain visible outside the closure.
Ready to break some reference cycles again? Good! This time, you’ll use — you guessed it — a capture list.
Unowned Self
Take another look at the code you have for your description lazy property on Tutorial:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Since the closure doesn’t exist after releasing the tutorial object from memory, self will never be nil, so you can change the strong reference to an unowned one using a capture list.
lazy var description: () -> String = {
[unowned self] in
"\(self.title) by \(self.author.name)"
}
Huzzah. No more reference cycle! All the deinit methods run and output the following to the console:
Memory management by Alice
Goodbye editor Ray!
Goodbye author Alice!
Goodbye tutorial Memory management!
Note: This is an excellent example of where using
unownedis safe. The benefits overweakare worth making the trade-off for.
Weak Self
Sometimes you can’t capture self as an unowned reference because it might become nil. Consider the following example:
let tutorialDescription: () -> String
do {
let author = Author(name: "Alice")
let tutorial = Tutorial(title: "Memory Management",
author: author)
tutorialDescription = tutorial.description
}
print(tutorialDescription())
The above code crashes your playground because tutorial and author are deallocated at the end of the do {} scope.
Change unowned to weak in the capture list of description to fix this:
lazy var description: () -> String = {
[weak self] in
"\(self?.title) by \(self?.author.name)"
}
This code produces the following curious output:
nil by nil
[weak self] means that the closure will not extend the lifetime of self. If the underlying object representing self goes away, it gets set to nil. The code doesn’t crash anymore but generates a warning you can fix.
The Weak-Strong Pattern
The weak-strong pattern (sometimes affectionately called the weak-strong dance) also does not extend the lifetime of self but converts the weak reference to a strong one after it enters the closure:
lazy var description: () -> String = {
[weak self] in
guard let self else {
return "The tutorial is no longer available."
}
return "\(self.title) by \(self.author.name)"
}
You’re using a guard to unwrap the weak self optional. In doing so, you’re creating a strong reference to self if it isn’t nil. Therefore, self is guaranteed to live until the end of the closure. You return a suitable descriptive string if self is nil.
Rules of Capturing self in Closures
There are a few rules to be aware of when capturing self in closures. The rules are there to help you avoid making accidental memory-management mistakes.
First, consider the following example:
class Calculator {
let values: [Int]
init(values: [Int]) {
self.values = values
}
func add() -> Int {
return values.reduce(into: 0) { $0 += $1 }
}
func multiply() -> Int {
return values.reduce(into: 1) { $0 *= $1 }
}
func calculate() {
let closure = {
let add = add()
print("Values added = \(add)")
let multiply = multiply()
print("Values multiplied = \(multiply)")
}
closure()
}
}
Here, the closure closure calls both add() and multiply() methods. If you try to use the above code, you’ll get some errors:
Call to method 'add' in closure requires explicit use of 'self' to make capture semantics explicit
Call to method 'multiply' in closure requires explicit use of 'self' to make capture semantics explicit
These are handy errors, though — it flags that you might not have noticed you’re capturing self because you haven’t written self anywhere in the closure code. It is implicitly captured through the calls to the methods add() and multiply(). It would also be the case if you were to access an instance variable of the class.
There are two ways to fix this error. You can either explicitly capture self, or you can write self. before each method call:
// Option 1: Explicitly capture `self`
func calculate() {
let closure = { [self] in
let add = add()
print("Values added = \(add)")
let multiply = multiply()
print("Values multiplied = \(multiply)")
}
closure()
}
// Option 2: Write `self.` before method calls
func calculate() {
let closure = {
let add = self.add()
print("Values added = \(add)")
let multiply = self.multiply()
print("Values multiplied = \(multiply)")
}
closure()
}
Either of these is fine to use — pick your preference. There’s a general trend toward not using self. explicitly, though, so Option 1 is more common.
There’s another small twist in all this, though! Notice the example above uses a class for the Calculator. If this were instead a struct, things would be different. For example, consider the following:
struct Calculator {
let values: [Int]
init(values: [Int]) {
self.values = values
}
func add() -> Int {
return values.reduce(into: 0) { $0 += $1 }
}
func multiply() -> Int {
return values.reduce(into: 1) { $0 *= $1 }
}
func calculate() {
let closure = {
let add = add()
print("Values added = \(add)")
let multiply = multiply()
print("Values multiplied = \(multiply)")
}
closure()
}
}
This code is the same as the previous example, except class changed to struct. The version with class failed to compile. However, the version with struct does compile. This difference is because Calculator is now a value type, and there’s no chance of a retain cycle in this scenario. Clever! :]
There’s no chance of a retain cycle in this example with a struct because you can’t have references to structs. Instead, if a struct is passed between two places — for example, set to a new variable or passed to a function — then a copy of the struct is taken. There are no references, so there can’t be any retain cycles.
Escaping Closures
In “Swift Fundamentals: Chapter 8 - Collection Iteration With Closures”, the closures you used as arguments were marked non-escaping. This designation means you can rest assured that a closure argument will not be called after the function returns. Such is the case for map, filter, reduce, sort and more.
If the closure argument is going to be used later, you must let the caller know you will grab a strong reference to it and extend its lifetime. You do this by marking the closure parameter with the @escaping attribute. A minimal example looks like this:
final class FunctionKeeper {
// 1
private let function: () -> Void
// 2
init(function: @escaping () -> Void) {
self.function = function
}
// 3
func run() {
function()
}
}
Here is what FunctionKeeper does:
- The stored property
functionkeeps a reference to a closure. - You pass a closure on initialization. Because it will put it into a stored property and your code will keep using it after
init(function:)returns, it must be marked as@escaping. - The
run()function executes the function.
You might use the function this way:
let name = "Alice"
let f = FunctionKeeper {
print("Hello, \(name)")
}
f.run()
This example creates a FunctionKeeper object and prints, “Hello, Alice”. The escaping closure extends the print closure’s lifetime and name variable by capturing it so it’s still available when run() executes. You should consider what it captures whenever you pass in an escaping closure since its lifetime can be arbitrarily extended.
Challenges
Before moving on, here are some challenges to test your memory-management knowledge. It’s best to try and solve them yourself, but solutions are available with the download or at the printed book’s source code link in the introduction if you get stuck.
Challenge 1: Break the Cycle
Break the strong reference cycle in the following code:
class Person {
let name: String
let email: String
var car: Car?
init(name: String, email: String) {
self.name = name
self.email = email
}
deinit {
print("Goodbye \(name)!")
}
}
class Car {
let id: Int
let type: String
var owner: Person?
init(id: Int, type: String) {
self.id = id
self.type = type
}
deinit {
print("Goodbye \(type)!")
}
}
var owner: Person? = Person(name: "Alice",
email: "alice@wonderland.magical")
var car: Car? = Car(id: 10, type: "BMW")
owner?.car = car
car?.owner = owner
owner = nil
car = nil
Challenge 2: Break Another Cycle
Break the strong reference cycle in the following code:
class Customer {
let name: String
let email: String
var account: Account?
init(name: String, email: String) {
self.name = name
self.email = email
}
deinit {
print("Goodbye \(name)!")
}
}
class Account {
let number: Int
let type: String
let customer: Customer
init(number: Int, type: String, customer: Customer) {
self.number = number
self.type = type
self.customer = customer
}
deinit {
print("Goodbye \(type) account number \(number)!")
}
}
var customer: Customer? = Customer(name: "George",
email: "george@whatever.com")
var account: Account? = Account(number: 10, type: "PayPal",
customer: customer!)
customer?.account = account
account = nil
customer = nil
Challenge 3: Break This Retain Cycle Involving Closures
Break the strong reference cycle in the following code:
class Calculator {
var result: Int = 0
var command: ((Int) -> Int)? = nil
func execute(value: Int) {
guard let command = command else { return }
result = command(value)
}
deinit {
print("Goodbye MathCommand! Result was \(result).")
}
}
do {
var calculator = Calculator()
calculator.command = { (value: Int) in
return calculator.result + value
}
calculator.execute(value: 1)
calculator.execute(value: 2)
}
Key Points
- Use a weak reference to break a strong reference cycle if a reference may become
nilat some point in its lifecycle. - Use an unowned reference to break a strong reference cycle when you know a reference always has a value and will never be
nil. - You must use
selfinside a closure’s body of a reference type. This requirement is a way the Swift compiler hints that you need to be careful not to make a circular reference. - Capture lists define how you capture values and references in closures.
- The weak-strong pattern converts a weak reference to a strong one.
- An escaping closure is a closure parameter that can be stored and called after the function returns. You should consider the capture list of escaping closures carefully because their lifetimes can be arbitrarily extended.