Swift 4 memory management via ARC for reference types (classes)

RELATED: Learn how to identify and fix memory leaks, specifically strong reference cycles, in “Fixing memory leaks — strong reference cycles — in Swift 4.”

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).

ARC is used for references types. Reference types are allocated on and deallocated from the heap. ARC is generally not used for value types. Value types are generally allocated on and deallocated from the stack. For a general discussion of memory organization, see this link.

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.

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.

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:

Let’s create an instance of class Widget in the playground and watch it get allocated and initialized:

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:

Let me rephrase the one-line creation of an instance of Widget in the playground for semantic clarity:

The output to console is still the same:

A static look at the console output is still the same:

#ad

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 type Widget; 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 named widgetReference.

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?

If you’re reading this and don’t know what scope is, you really really need to brush up on your computer science basic principles. I leave it to you to do some research and/or refresh your memory (the latter link is for Swift).

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:

I’ll add two comments to help you understand ARC in the code snippet I just displayed:

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:

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:

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

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:

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:

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.

#ad

Let’s look at a scenario that proves the ARC is involved at local scope:

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…

… 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.

#ad

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 https://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.