Understanding Swift Memory Management

What Is Automatic Reference Counting (ARC)

Understanding Swift Memory Management
1 What Is Automatic Reference Counting (ARC) 11:30

Before we can discuss Automatic Reference Counting, ARC for short, you need to understand the differences between value types and reference types.

Value Types and Reference Types

You are probably already familiar with classes. Most object-oriented programming languages, such as Ruby, PHP, C#, and Java, define classes. Unless you have experience with C or Objective-C, structures may be new to you.

Even though classes and structures are similar, they differ in a number of key aspects. The difference I would like to focus on today is how they are stored in memory.

I won't explain in detail what separates classes and structures. In this episode, we explore how classes and structures differ in the context of memory management. While this may sound like an advanced topic, it certainly isn't. It is essential that you understand how value types and reference types behave in Swift.

Passing by Value

Structures are an integral part of the Swift programming language. Even if you are new to Swift, chances are that you have already been using structures. Fire up Xcode and create a playground by choosing the Blank template from the iOS > Playground section.

Creating a Playground in Xcode 11

This is what a playground looks like in Xcode if you choose the Blank template, an import statement for the UIKit framework at the top and a variable declaration. Rename variable str to string1.

import UIKit

var string1 = "Hello, playground"

The variable string1 is of type String. In Swift, strings are structures. Press Option, click the string1 variable, and click the String symbol to bring up Xcode's documentation browser.

Bring Up Xcode's Documentation Browser

Bring Up Xcode's Documentation Browser

As you can see at the top, String is a structure. What does that mean? And why is it important for this discussion? Instances of structures and enumerations are passed by value. What that means is best illustrated with an example.

What Does Passing by Value Mean?

Let's declare a second variable, string2, and assign the value of string1 to the new variable.

var string1 = "Hello, playground"
var string2 = string1

The value stored in string1 is equal to the value stored in string2. This if statement proves this.

if string1 == string2 {
    print("\(string1) is equal to \(string2)")
} else {
    print("\(string1) is NOT equal to \(string2)")
}

What happens if we assign a new value to string1. Below the if statement, modify the value stored in string1.

string1 = "Hello, world"

What do you expect the value stored in string2 is? Move the if statement you added a moment ago below the assignment of string1. This is what the playground should look like.

import UIKit

var string1 = "Hello, playground"
var string2 = str

string1 = "Hello, world"

if string1 == string2 {
    print("\(string1) is equal to \(string2)")
} else {
    print("\(string1) is NOT equal to \(string2)")
}

Are you surprised by the result you see in the results panel on the right? I repeat what I mentioned earlier. Instances of structures and enumerations are passed by value. Let me explain what this means using the playground.

Remember that we stored ā€¯Hello, playground" in the variable string1. We then assigned the value of string1 to string2. Under the hood, the value of string1 is copied and stored in string2. This means that separate values are stored in string1 and string2. The values are equal, but, in memory, they are different. That is what passing by value means. When the value of string1 was assigned to string2, it was passed by value.

What Does Passing by Reference Mean?

Don't worry if it doesn't click yet. The next example should fill in any remaining gaps. Clear the playground and define a class named Person. Don't worry about the syntax if you are new to Swift.

class Person {

    var first: String
    var last: String

    init(first: String, last: String) {
        self.first = first
        self.last = last
    }

}

The Person class defines two variable properties of type String, first and last. We also define an initializer that accepts two arguments of type String.

Let's create an instance of the Person class and output the values stored in its first and last properties.

let jim = Person(first: "Jim", last: "Simmons")
jim.first   // "Jim"
jim.last    // "Simmons"

We now declare a second constant, jane, and assign the value stored in jim to jane. It should be no surprise that the values stored in the first and last properties of jane are equal to those of jim.

let jane = jim
jane.first  // "Jim"
jane.last   // "Simmons"

Observe what happens if we modify the first name of Jane. Are you surprised by the result?

jane.first = "Jane"

jim.first   // "Jane"
jane.first  // "Jane"

The value stored in the first property of jim is also modified. Why is that? Instances of classes are passed by reference. When we assigned jim to jane, the value stored in jim was not copied. Instead, the reference of the Person instance stored in jim was assigned to jane. This means that jim and jane are pointing to the same Person instance. Only one instance of the Person class is present in memory.

Memory Management

You may be wondering why it is important to know and understand the differences between value types and reference types. Anyone working with Swift should know about the differences between value and reference types. As I mentioned earlier, the differences are especially important in the context of memory management.

Let's start with some good news. You don't need to worry about the memory management of value types, that is, structures and enumerations. The compiler is in charge of the memory management of structs and enums. Because they are passed by value, the compiler knows and understands when to dispose of instances of structs and enums.

This isn't true for class instances, though. Most of the time, you don't need to worry about the memory management of class instances. While this is good news, it doesn't mean that you shouldn't need to worry about memory management. Let me explain why that is.

Automatic Reference Counting (ARC)

It is essential that an object, an instance of a class, structure, or enumeration, is deallocated when it is no longer needed. When an object is deallocated, the memory occupied by the object is freed. This is instrumental for the performance and efficiency of the system.

Because class instances are passed by reference, it is possible for properties, constants, and variables to keep a reference to the same class instance. This is a key aspect of object-oriented programming. This is incredibly useful, but it makes it difficult for the compiler to know and understand when it is safe to deallocate a class instance.

To make the compiler's task easier, Swift uses Automatic Reference Counting, ARC for short. Apple first introduced Automatic Reference Counting in Mac OS X Snow Leopard and iOS 4. A more mature version of ARC was made available in Mac OS X Lion and iOS 5. This was a very important change for the developer community.

The idea underlying ARC is simple. It keeps track of class instances and it decides when it is safe to deallocate the class instances it monitors. It does this by counting the references of each class instance. Let me illustrate this with an example.

We define a class, Device. The class has two properties of type String, model and manufacturer. We can initialize an instance of the Device class by invoking the init(model:manufacturer:) initializer.

class Device {

    let model: String
    let manufacturer: String

    init(model: String, manufacturer: String) {
        self.model = model
        self.manufacturer = manufacturer
    }

}

Before we create an instance of the Device class, we declare two variables of type Device?.

var deviceJim: Device?
var deviceJane: Device?

We create an instance of the Device class and assign it to deviceJim. Because the deviceJim variable holds a reference to the Device instance we created, the Device instance isn't deallocated.

deviceJim = Device(model: "iPhone 7", manufacturer: "Apple")

Automatic Reference Counting ensures a class instance isn't deallocated as long as a property, constant, or variable keeps a strong reference to the class instance. We discuss strong references later.

As the name implies, Automatic Reference Counting keeps count of the number of references to a class instance. It does this to understand when it is safe to dispose of a class instance.

We can store another reference to the Device instance we created in the deviceJane variable. Both deviceJim and deviceJane point to the same Device instance. Only one Device instance is currently in memory.

deviceJane = deviceJim

We can decrement the reference count of the Device instance by setting deviceJim to nil. But because deviceJane continues to hold a reference to the Device instance, it isn't deallocated. This also proves that deviceJim and deviceJane were pointing to the same Device instance.

deviceJim = nil

The moment we set deviceJane to nil, no properties, constants, or variables keep a reference to the Device instance. As a result, the Device instance is deallocated. It also means that the memory taken up by the Device instance is freed. That is the goal of Automatic Reference Counting.

You can verify this by adding a deinitializer to the Device class. The deinitializer function of a class is invoked immediately before an instance of the class is deallocated. It gives the instance an opportunity to clean up any resources it has used during its lifetime.

class Device {

    let model: String
    let manufacturer: String

    init(model: String, manufacturer: String) {
        self.model = model
        self.manufacturer = manufacturer
    }

    deinit {
        print("The device is about to be deallocated.")
    }

}

In the deinit method, we add a print statement that informs us the class instance is about to be deallocated. If we don't set the deviceJane variable to nil, the deinitializer isn't invoked. This means the class instance isn't deallocated.

What Is Automatic Reference Counting

If we set deviceJane to nil, no properties, constants, or variables are holding on to the Device instance. As a result, the Device instance is deallocated and the deinitializer is invoked.

What Is Automatic Reference Counting

But Automatic Reference Counting Needs Your Help

There are times that Automatic Reference Counting isn't able to figure out when it is safe to deallocate a class instance. To avoid retain cycles and memory leaks, Automatic Reference Counting needs your help. In the next episode of this series, we zoom in on retain cycles.