LEARN SWIFT

Buy the PDF

Chapter 17 Memory Management

Swift uses a system of memory management called Automatic Reference Counting (ARC). ARC keeps track of how many references there are to an object in your code. When you create a new reference to something the reference count for that object increases. When a reference to an object goes out of scope and is no longer being used the reference count for it decreases. When the reference count for something reaches zero the memory assigned to that something gets released. ARC handles this process of counting references to objects automatically.

ARC is mostly automatic. In general you don’t need to manage memory yourself and you can leave Swift to take care of it automatically. However there are some cases where you may need to get involved in the memory management of objects manually.

17.1 Retain cycles and memory leaks

A situation where ARC fails and memory leaks can occur is when we end up with a retain cycle. A retain cycle happens when two objects have strong references to each other that prevent them from being released. Lets look at an example.

Note: For the examples in this chapter you should create a command line application to run them. If you run them in a playground you may not see the expected behaviour as the playground maintains references to variables during the course of evaluating your code, and so memory management doesn’t behave the same way in a playground as it does outside a playground.

 1 print("Program starting")
 2 
 3 class Pet{
 4     var name:String
 5     var bestFriend:Pet?
 6 
 7     init(name: String){
 8         self.name = name
 9     }
10 
11     deinit{
12         print("Object with name \(name) is being released")
13     }
14 }
15 
16 var tom:Pet?
17 var jerry:Pet?
18 
19 tom = Pet(name: "Tom")
20 jerry = Pet(name:"Jerry")
21 
22 tom = nil
23 jerry = nil
24 
25 print("Program ending")

This example shows ARC working correctly. When we run this program we will see the following output in the console:

Program starting
Object with name Tom is being released
Object with name Jerry is being released
Program ending

We create 2 objects named tom and jerry (they are declared optionals so that we can assign nil to them). Our Pet class has a deinitializer that prints out a message to let us know when objects are being released (line 11). Each of these objects has a reference count of 1 after we assign them to variables (lines 16-17). After assigning the 2 objects to variables tom and jerry, we change both those variables to nil at lines 22-23. Now these variables no longer refer to the two objects, and the reference count for each object decreases by 1. So each now has a reference count of 0 and gets released. In our program output we see that this is the case and the two objects got released once we were no longer referring to them.

Here’s another version of that program that introduces a circular dependency that results in a retain cycle.

 1 print("Program starting")
 2 
 3 class Pet{
 4     var name:String
 5     var bestFriend:Pet?
 6 
 7     init(name: String){
 8         self.name = name
 9     }
10 
11     deinit{
12         print("Object with name \(name) is being released")
13     }
14 }
15 
16 var tom:Pet?
17 var jerry:Pet?
18 
19 tom = Pet(name: "Tom")
20 jerry = Pet(name:"Jerry")
21 
22 jerry?.bestFriend = tom
23 tom?.bestFriend = jerry
24 
25 tom = nil
26 jerry = nil
27 
28 print("Program ending)

When we run this program we see the following output:

Program starting
Program ending

This time our objects didn’t get released. Lets walk through this example with memory management in mind. At lines 19 and 20 we assign Pet objects to vars tom and jerry. At this point each of these objects has a reference count of 1. Now at lines 22 and 23 we set tom as jerry’s best friend and jerry as tom’s best friend. So now tom’s bestFriend property points at the same object as the variable jerry and the reference count for that object is 2. And jerry’s bestFriend property points at the same object as the variable tom and that object’s reference count is also 2.

Now at lines 25 and 26 we set both tom and jerry to nil. Both of the objects that these variables were pointing to get their reference count decreased by 1. But both currently have a reference count of 2, so decreasing by 1 means that their reference count is now 1, even though there are no remaining objects that reference them. We’ve created a retain cycle that prevented the objects from getting released.

If this happens with a lot of objects over the course of your program, or if your program runs for a long time, this can result in a memory leak. ARC cannot figure out the retain cycle by itself so it needs some programmer intervention to help it out. We do this by distinguishing between strong and weak references in our code.

17.2 Strong and Weak references

By default references that are created are strong references. Strong references increase the reference count for an object and the presence of an active strong reference prevents an object from being deallocated.. Weak references are created using the weak keyword. By marking a reference as weak you are telling Swift not to increase the reference count for the object.

By not increasing the reference count we are saying that if there are no other references to an object in the program, we don’t need to keep that object around just on account of this reference. Also by declaring a reference as weak we are saying that we are ok with the object that is referred to disappearing. This has the follow on effect that weak references must always be declared as optionals. If a weak reference points to something and that something disappears, the weak reference will be changed to nil. If you want to declare a reference to an object without increasing the reference count for that object, but you don’t want that object to ever be nil once it is assigned, you can create an unowned reference instead of a weak one.

We fix our previous example by using weak references. We use the weak keyword to indicate that bestFriend should be a weak reference to our Pet object.

print("Program starting")

class Pet{
    var name:String
    weak var bestFriend:Pet?

    init(name: String){
        self.name = name
    }

    deinit{
        print("Object with name \(name) is being released")
    }
}

var tom:Pet?
var jerry:Pet?

tom = Pet(name: "Tom")
jerry = Pet(name:"Jerry")

jerry?.bestFriend = tom
tom?.bestFriend = jerry

tom = nil
jerry = nil

print("Program ending")

Once we make this change, and run our program we see that both objects get released again.

Program starting
Object with name Tom is being released
Object with name Jerry is being released
Program ending

Our circular dependency no longer prevents the objects from being released.