Fixing memory leaks — strong reference cycles — in Swift 4

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?


Here’s the console output for the previous code snippet:

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:

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:

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:

Look at the console output. No objects ever get deallocated.

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:”

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:

Here’s the console output from the previous code snippet:

Notice that if I use local scope, I don’t have to forcibly deallocate anything. The Swift compiler and runtime handles everything:

   

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:

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.

Author: Andrew Jaffee

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by http://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.

Leave a Reply

Your email address will not be published. Required fields are marked *