PREREQUISITES FOR THIS TUTORIAL
If you don’t understand how Swift manages memory for classes (reference types), you need to read the first article in this series, “Swift 4 memory management via ARC for reference types (classes)”. Once you understand how reference type-based memory is managed with ARC, you’ll be ready to understand how to prevent memory leaks from occurring in your app. Remember that ARC only applies to reference (class
) types, not value types like struct
and enum
instances.
THE ARC DEALLOCATION PROCESS
Remember how we visualized the reference count that ARC maintains on the allocated instance of reference type Widget
in my introductory ARC tutorial?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... // reference count is zero; nothing allocated widgetReference = Widget(name: "eraser", manufacturer: "Acme") // reference count is now 1 widgetReference1 = widgetReference // reference count is now 2 widgetReference2 = widgetReference // reference count is now 3 widgetReference = nil // reference count is now 3-1 = 2 widgetReference1 = nil // reference count is now 2-1 = 1 widgetReference2 = nil // reference count is now 1-1 = 0 |
Here’s the console output for the previous code snippet:
1 2 3 |
Widget eraser instance created. Widget eraser instance deallocated. |
Let’s watch the forced deallocation of the global Widget
instance with commentary:
Introducing the weak reference
Before discussing weak
, let’s look at an example — a variation of the previously-shown code snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var widgetReference: Widget? var widgetReference1: Widget? var widgetReference2: Widget? weak var widgetReference3: Widget? // reference count is zero; nothing allocated widgetReference = Widget(name: "eraser", manufacturer: "Acme") // reference count is now 0+1 = 1 widgetReference1 = widgetReference // reference count is now 1+1 = 2 widgetReference2 = widgetReference // reference count is now 2+1 = 3 widgetReference3 = widgetReference // reference count is now 3+0 = 3 widgetReference = nil // reference count is now 3-1 = 2 widgetReference1 = nil // reference count is now 2-1 = 1 widgetReference2 = nil // reference count is now 1-1 = 0 widgetReference3 = nil // reference count is now 0-0 = 0 |
You should notice that I declared a new weak
variable named widgetReference3
on line 4, made it a reference to the global Widget
instance on line 13, and then set widgetReference3
to nil
on line 22.
Watch this code in action:
The Swift 4.2 language documentation states:
A weak reference is a reference that does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance. This behavior prevents the reference from becoming part of a strong reference cycle. You indicate a weak reference by placing the weak
keyword before a property or variable declaration.
Because a weak reference does not keep a strong hold on the instance it refers to, it’s possible for that instance to be deallocated while the weak reference is still referring to it. Therefore, ARC automatically sets a weak reference to nil
when the instance that it refers to is deallocated. And, because weak references need to allow their value to be changed to nil
at runtime, they are always declared as variables, rather than constants, of an optional type.
You see that the weak
variable named widgetReference3
“does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance.” I hope you see where I’m going with this. Defining a variable as weak
will help us prevent a memory leak in this tutorial.
MEMORY LEAKS IN CLASSES
I’m going to describe a use case for which I’ll model in Swift code, set the stage for a memory leak, show you why the code causes a memory leak, and then show you how to plug the memory leak by using a weak reference.
Use case for code with memory leak
To illustrate the ease with which developers can write code that causes memory leaks, I came up with a use case that represents a scenario that could commonly occur in everyday life — and therefore applies to developers who are tasked with modeling such everyday life scenarios.
Let’s consider everyday life at a college or university.
I’ll define the very common relationship between students and teachers and how that relationship is tracked by the school in software. Every academic institution needs to know if a student is taking a class with which a particular teacher, or, to look at things slightly differently, given a teacher’s name, get a list of all her/his students.
Notice I haven’t taken into account a class’s name or the course’s unique identifier. I haven’t specified anything about maximum class size, its credit hours, or schedule. There’s nothing in my code about tuition, payments made, teacher addresses, etc. There’s no student ID number. I’m keep the Swift code simple so that we can concentrate on Swift memory management, not on building an academic institution enterprise resource planning (ERP) system. So yes, this code is oversimplified, but it’s supposed to be so you can concentrate on memory management.
I can create an instance of Student
that has a name
property and optional teacher
property. The teacher
property is optional — can be nil
— as a student may be between semesters or quarters and, while still enrolled, may not be currently taking a course. Obviously, the name
property is used to identify the student.
I can also create an instance of Teacher
that has a name
property to identify the teacher. There’s also an students
property which is an optional array of type Student
. The students
property is optional — can be nil
— as a teacher may be between semesters or quarters, or on sabbatical, and, while still employed, may not be currently teaching a course.
The classes (reference types)
Here are the classes I just described in my use case, but materialized in the form of Swift language code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
class Student { let name:String var teacher:Teacher? init(name: String, teacher: Teacher) { self.name = name self.teacher = teacher print("Student \(name) instance allocated.\n") } deinit { print("Student \(name) instance deallocated.\n") } } class Teacher { let name:String var students:[Student]? init(name: String) { self.name = name print("Teacher \(name) instance allocated.\n") } deinit { print("Teacher \(name) instance deallocated.\n") } } |
So while overly simplistic, this code represents a model most of you can identify with from your personal experience. After just glancing at this code, you can see why it’s generally reasonable. You get the idea.
The memory leak
Let’s see how easily we can get into trouble. I’m going to create a teacher, create some students, associate those students with the teacher, and then try to forcibly deallocate all the class instances (objects) I created. All objects get allocated, no errors occur, but none of the classes I allocate ever get deallocated. A memory leak occurred. Take a look at the following code and corresponding console output. This code was written at the global scope of an Xcode 9 playground so that I had to opportunity to show you stepwise manual object allocation and deallocation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// create a teacher object var george:Teacher? george = Teacher(name: "George Washington") // create some student objects var james:Student? = Student(name: "James Adams", teacher: george!) var thomas:Student? = Student(name: "Thomas Jefferson", teacher: george!) var benjamin:Student? = Student(name: "Benjamin Franklin", teacher: george!) var students:[Student]? = [james!, thomas!, benjamin!] // associate the students with the teacher george?.students = students // forcibly deallocate the students james = nil thomas = nil benjamin = nil students = nil // an array maintains strong references // forcibly deallocate the teacher george = nil |
Look at the console output. No objects ever get deallocated.
1 2 3 4 5 6 7 |
Teacher George Washington instance allocated. Student James Adams instance allocated. Student Thomas Jefferson instance allocated. Student Benjamin Franklin instance allocated. |
PLUGGING MEMORY LEAKS
Nothing got deallocated. The memory that was allocated is now lost to this app for the duration of its lifetime. If you’ve designed bulky classes capable of storing significant amounts of data, such memory leaks could lead to diminished app performance, and low memory warnings. Remember what Apple says: “Failure to reduce your app’s memory usage may result in your app’s termination.”
My original Student
and Teacher
classes demonstrate what Apple calls a “strong reference cycle:”
…it’s possible to write code in which an instance of a class never gets to a point where it has zero strong references. This can happen if two class instances hold a strong reference to each other, such that each instance keeps the other alive.
Look at my two class definitions above. Do you see the strong reference cycles? Remember that, as Apple explains it:
… if ARC were to deallocate [a class] instance that was still in use, it would no longer be possible to access that instance’s properties, or call that instance’s methods. Indeed, if you tried to access the instance, your app would most likely crash. …
… whenever you assign a class instance to a property, constant, or variable, that property, constant, or variable makes a strong reference to the instance. The reference is called a “strong” reference because it keeps a firm hold on that instance, and does not allow it to be deallocated for as long as that strong reference remains.
Visualizing strong reference cycles
Let’s look at strong reference cycles visually and you’ll understand the concept in more concrete terms:
Now you should see why this is called a “strong reference cycle.” As Apple puts it, “Unfortunately, linking these two instances creates a strong reference cycle between [the two classes]. … when you break the strong references held by [setting the variables to nil
], the reference counts do not drop to zero, and the instances are not deallocated by ARC:”
1 2 3 4 5 6 7 8 |
// forcibly deallocate the students james = nil thomas = nil benjamin = nil students = nil // an array maintains strong references // forcibly deallocate the teacher george = nil |
Solving strong reference cycles with weak
What to do? Fortunately, Swift has a solution. I showed you the solution at the beginning of this tutorial: using a weak reference by declaring a variable with the weak
keyword. Remember Apple’s definition which I posted above. A weak
variable “does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance.”
Here’s the new code — and the only change I made was to line 4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
class Student { let name:String weak var teacher:Teacher? init(name: String, teacher: Teacher) { self.name = name self.teacher = teacher print("Student \(name) instance allocated.\n") } deinit { print("Student \(name) instance deallocated.\n") } } class Teacher { let name:String var students:[Student]? init(name: String) { self.name = name print("Teacher \(name) instance allocated.\n") } deinit { print("Teacher \(name) instance deallocated.\n") } } // create a teacher object var george:Teacher? george = Teacher(name: "George Washington") // create some student objects var james:Student? = Student(name: "James Adams", teacher: george!) var thomas:Student? = Student(name: "Thomas Jefferson", teacher: george!) var benjamin:Student? = Student(name: "Benjamin Franklin", teacher: george!) var students:[Student]? = [james!, thomas!, benjamin!] // associate the students with the teacher george?.students = students // forcibly deallocate the students james = nil thomas = nil benjamin = nil students = nil // an array maintains strong references // forcibly deallocate the teacher george = nil |
Here’s the console output from the previous code snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Teacher George Washington instance allocated. Student James Adams instance allocated. Student Thomas Jefferson instance allocated. Student Benjamin Franklin instance allocated. Teacher George Washington instance deallocated. Student James Adams instance deallocated. Student Thomas Jefferson instance deallocated. Student Benjamin Franklin instance deallocated. |
Notice that if I use local scope, I don’t have to forcibly deallocate anything. The Swift compiler and runtime handles everything:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
do { // create a teacher object var george:Teacher? george = Teacher(name: "George Washington") // create some student objects var james:Student? = Student(name: "James Adams", teacher: george!) var thomas:Student? = Student(name: "Thomas Jefferson", teacher: george!) var benjamin:Student? = Student(name: "Benjamin Franklin", teacher: george!) var students:[Student]? = [james!, thomas!, benjamin!] // associate the students with the teacher george?.students = students } |
With few exceptions, you’re more likely to be using something more restrictive than global scope in your apps. The output is the same, though:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Teacher George Washington instance allocated. Student James Adams instance allocated. Student Thomas Jefferson instance allocated. Student Benjamin Franklin instance allocated. Teacher George Washington instance deallocated. Student James Adams instance deallocated. Student Thomas Jefferson instance deallocated. Student Benjamin Franklin instance deallocated. |
CONCLUSION
Everything, variable or constant, that we set equal to a class instance is a reference. That’s the beauty — and danger — of reference types: we can have multiple pointers to a single class instance and we can pass those references around in our code. References types can be extremely efficient because we can pass a pointer to an object all around an app with very low overhead — but not without a price.
We all know that software development is a complex endeavor and it just keeps getting more complex. We’ve been using references semantics for years, and some software architects, software developers, and software language architects have realized that too much time has been spent fixing code that has bugs because of reference semantics. When writing thousands, hundreds of thousands, and millions of lines of code, it’s not that hard to end up with difficult-to-debug reference semantics-based problems. Today, we remedied screwy interdependencies between classes (retain cycles) and in an upcoming post, we’ll tackly multiple threads erroneously accessing the same instances (objects) of class
types.
These problems from reference semantics have led some software professionals, such as at Apple, to start thinking about moving to value semantics, despite their huge investment in reference type-based code.
There’s a reason that I’ve spent a lot of time on this blog talking about reference types versus value types, reference semantics versus value semantics, “local reasoning,” and protocol-oriented programming versus object-oriented programming (see comparison of POP and OOP here). A seismic shift is taking place in the software development world. This shift is mainly due to all the people using the Swift language and the people developing and improving the language. Many programmers and desigers are starting to make the shift from using reference-based, class
type-based, and OOP-based development to using value-based, struct
type-based, and POP-based development.
Indeed, one way to try to avoid reference-based memory leaks is to abandon reference semantics completely and write your apps using value semantics. But since classes are still used extensively in the iOS SDKs, and since there are many good reasons to use classes, going 100% value types and semantics seems a bit extreme.
By reading this tutorial, and reviewing all the links I just provided, you should get a strong hint as to why this shift to value-based development is taking place.
Finally, since reference semantics will be around for awhile, we’ll be talking about solving another reference type-based problem, “Strong Reference Cycles for Closures,” soon.