Most developers assume that Swift is a “safe” language in terms of memory management. That’s probably true most of the time, but there are some notable exceptions, especially when dealing with certain scenarios when using what are called “reference types” and “reference semantics.”
This tutorial is ultimately meant to prepare you for in-depth discussion of what seems to be inevitable shift from reference semantics to value semantics. Before I plunge into this software development paradigm shift, I want to provide you with a strong and understanding of how memory is managed when you create and use instances of the class
type, a reference type. Oftentimes, reference-based memory management “just works” … until it doesn’t. Things don’t work mainly in situations where class
types are designed with interdependencies and/or when multiple threads access the same instance (object) of a class
type, and you get “memory leaks.” Swift manages reference-based memory with a technology Apple has dubbed “Automatic Reference Counting” (ARC). Before even begin talking about debugging memory leaks, you must understand ARC.
This discussion does not pertain to memory management for value types (struct
, enum
), but note that I will mention value types and value semantics several times in terms of comparison to reference types and reference semantics.
DEFINITIONS: SWIFT MEMORY MANAGEMENT VIA ARC
Remember that “Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage.” ARC only applies to class
types. (We won’t be getting into complicated scenarios where, say, a class
contains a struct
or enum
or vice versa).
Every time you declare a constant (let
) or variable (var
) instance of a class
(an “object”), ARC allocates memory for the instance and stores some meta data about that instance. When your code is done using that instance, ARC frees up — deallocates — the memory that the instance was using. Whenever you create a reference to that instance, ARC adds 1 (one) to a counter that is maintained for tracking the memory used by that instance. As long as there is 1 (one) or more reference(s) to that instance, its memory cannot be deallocated.
Most of the time, you don’t have to worry about memory management of reference (or value) types. Memory for instances of class
types is allocated when needed and deallocated when no longer needed. That does sound a bit flippant, so I’ll provide some details regarding phrases like “when needed” and “when no longer needed” as we get deeper into this tutorial.
… 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.
Keep that strong reference phrase in mind as I guide you through this tutorial. Also think about what exactly constitutes the idea that an object is “still in use.”
A note about deallocation
I’ll add “deinitializers” to my classes in this tutorial’s code solely for the purpose of allowing me to demonstrate when class
instances are deallocated from memory.
In Swift, we can attempt to force the deallocation of a class
instance, regardless of scope. We do this by first declaring a reference: a variable (var
) that is an optional where the variable’s type is the same as the class
of interest. Then we initialize an instance of the class
of interest and set it equal to our optional reference variable. When ready to attempt to force deallocation of the class
instance, we set the optional reference variable to nil
. Notice I say attempt because we can only achieve deallocation when ARC notices that the class instance has reached a reference count of 0 (zero).
That’s something nice about Swift. In ARC memory management, “values are deallocated as soon as their last strong reference is removed.” That’s probably good for performance, maintaining as much free memory as possible, and also good for me to show you how memory management for reference types works in Swift.
AN EXAMPLE OF ARC
Let’s start coding in an Xcode playground. You may ask, “Why is he emphasizing a playground?” You’ll see soon. As we move forward, I’ll define a simple class called Widget
, declare an instance of it, declare some references to the instance, and show you how ARC keeps the original instance alive as long as there are references to it. But one step at a time…
Here’s my class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Widget { var name: String var manufacturer: String init(name: String, manufacturer: String) { self.name = name self.manufacturer = manufacturer print("Widget \(name) instance created.\n") } deinit { print("Widget \(name) instance deallocated.\n") } } |
Let’s create an instance of class Widget
in the playground and watch it get allocated and initialized:
1 2 |
// 1.0 let widgetReference = Widget(name: "eraser", manufacturer: "Acme") |
Here’s the output from my code snippet, the print
statement from the class’s initializer executing:
Here’s a static look at the console output:
1 |
Widget eraser instance created. |
Let me rephrase the one-line creation of an instance of Widget
in the playground for semantic clarity:
1 2 |
var widgetReference: Widget? // 2.1 widgetReference = Widget(name: "eraser", manufacturer: "Acme") // 2.2 |
The output to console is still the same:
A static look at the console output is still the same:
1 |
Widget eraser instance created. |
By using more verbose Swift, I’m explicitly showing you that:
- I’m declaring a variable named
widgetReference
that can hold an optional reference to an instance of class typeWidget
; and, - I’m creating an instance of class type
Widget
, initializing it, allocating memory for it, and assigning a reference to it to the variable namedwidgetReference
.
Remember that quote from Apple: “… whenever you assign a class instance to a property, constant, or variable, that property, constant, or variable makes a strong reference to the instance.” So in each of the lines I’ve commented as “1.0” and “2.2” above, widgetReference
becomes a strong reference to an instance of class Widget
and ARC increments the reference count for Widget
from 0 (zero) to 1 (one).
Sometimes people get confused when discussing references, strong references, instances, classes, reference types… Here’s diagram that should make the current discussion crystal clear:
A Swift reference is basically like a pointer to an instance of class (object), though don’t confuse Swift references with pointers in more “raw” languages like C and C++. You can declare many references to the same object. All those references point to the same object. Anything you do with any of those references, like setting a property, will change the one class instance. If you pass one of those references into a function that has code that changes say, a property of that reference, then the property of the one class instance will be changed.
ARC and scope
Why don’t we see the Widget
instance get deinitialized and deallocated in the last section? We don’t see “Widget eraser instance deallocated” printed to the console. Could it have to do with scope?
Everything you declare at the “top level” of an Xcode playground, like my one line or two line instantiation of the Widget
instance above, occurs in global scope:
I assume that my Widget
instance is deallocated whenever I rerun or close the playground, but I never see the print
output from my class’s deinitializer. While a playground is open, it always has scope, otherwise it wouldn’t work as … a playground. You have to have scope in a playground, otherwise it wouldn’t compile and execute commands as you type them. A playground continually compiles the Swift code you type into the editor and executes it (unless you specify otherwise).
Remember that I can create local scope in a playground.
Also remember that, just because a reference type instance goes out of scope, there’s no guarantee that it will be deallocated. It might have a reference count of 1+. This is one of the problems I mentioned earlier about reference types. We’ll discuss those problems later. First, you need to understand ARC.
ARC at global scope
Let’s take advantage of global scope to prove a point. In the example below, I want an instance of Widget
to hang around in global scope so I can “manually” deallocate it. Remember how I talked about forcibly deallocating an object? I’ll also show you multiple references to an object and ARC in action.
I’ll declare three optional variables of reference type Widget
. I’ll initialize/allocate an instance of Widget
and get a reference to it in the variable named widgetReference
. Then I’ll create two other references to the instance of Widget
and name them widgetReference1
and widgetReference2
:
1 2 3 4 5 6 7 |
var widgetReference: Widget? var widgetReference1: Widget? var widgetReference2: Widget? widgetReference = Widget(name: "eraser", manufacturer: "Acme") widgetReference1 = widgetReference widgetReference2 = widgetReference |
I’ll add two comments to help you understand ARC in the code snippet I just displayed:
1 2 3 4 5 6 7 8 9 10 |
var widgetReference: Widget? var widgetReference1: Widget? var widgetReference2: Widget? // there's no reference to a Widget instance yet widgetReference = Widget(name: "eraser", manufacturer: "Acme") // now there's a strong reference to a Widget instance widgetReference1 = widget widgetReference2 = widget |
Here’s what happens when the Widget
instance is allocated and initialized — same as before:
Reference count in global scope
Let me show you the reference count in global scope using the example code from the preceding section:
1 2 3 4 5 6 7 8 9 10 |
var widgetReference: Widget? var widgetReference1: Widget? var widgetReference2: Widget? 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 |
Deallocation in global scope
Remember my discussion of forcing the deallocation of a reference type instance by setting optional variables referring to that instance to nil
? How about we try to force the deallocation of the first global Widget
instance? Watch:
Notice that the Widget
type’s deinit
method was not called when we set widgetReference
to nil
? That was the first variable we set. It held a reference to the initialized and allocated Widget
instance. Being set first didn’t make it special and widgetReference
is just a reference (basically, a pointer). The instance of Widget
was not deallocated until all references to it, namely widgetReference1
and widgetReference2
were set to nil
. That’s ARC.
Visualizing the ARC deallocation process
Here’s a way to visualize the reference count that ARC maintains on the allocated instance of reference type Widget
:
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:
ARC and local scope
So let’s create a local scope in the form of a function. This function declares an instance of class Widget
and initializes/allocates it. I’ve prepared a call to invoke the function but left it commented out so I could show you what happens in real time:
1 2 3 4 5 6 7 8 9 10 |
func useWidget() { // widget reference pushed on the stack; no // instance of Widget created yet var widget: Widget? // instance of Widget created on the heap widget = Widget(name: "eraser", manufacturer: "Acme") } // useWidget() |
Notice I’ve added some commentary to the code for those of you who are really interested in the mechanics of memory management. I’ll leave those comments for you to ponder.
Here’s what happens when I uncomment the call to my function useWidget()
:
Here’s the static output as written to the console:
1 2 3 |
Widget eraser instance created. Widget instance deallocated. |
The Widget
instance got deallocated because it went out of scope. The function useWidget
brought the class
instance into scope. When the function exited, the Widget
instance went out of scope and was deallocated. ARC determined that the Widget
instance had a reference count of 1 (one) while inside the function, but that reference count dropped to 0 (zero) when the function exited. Strictly speaking, the reference count to the Widget
instance dropped to 0 (zero) when the reference, the widget
variable, was popped off the stack.
Let’s look at a scenario that proves the ARC is involved at local scope:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var widgetReference: Widget? func useWidget() { // widget reference pushed on the stack; no // instance of Widget created yet var widget: Widget? // instance of Widget created on the heap widget = Widget(name: "eraser", manufacturer: "Acme") // referece count is now 1 widgetReference = widget // refernece count is now 2 } // exit function; referece count is now 1 // reference count is now 0 useWidget() // reference count is now 1 |
Notice that the Widget
instance was not deallocated. Even after the function exits, the Widget
instance has a reference count of 1 (one). Watch:
You should know by now how I could force the global Widget
instance, originally created at local scope, to be deallocated, right? Review this code…
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? func useWidget() { // widget reference pushed on the stack; no // instance of Widget created yet var widget: Widget? // instance of Widget created on the heap widget = Widget(name: "eraser", manufacturer: "Acme") // referece count is now 1 widgetReference = widget // refernece count is now 2 } // exit function; referece count is now 1 // reference count is now 0 useWidget() // reference count is now 1 widgetReference = nil // reference count is now 0 |
… and watch as I execute the code snippet I just showed you:
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. Remember I mentioned screwy interdependencies between classes (retain cycles) and/or 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.
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, join me to discuss finding and fixing reference type-based problems, specifically strong reference cycles, at this link.