(iOS) ARC: Memory Management in iOS

Kenta Kodashima
7 min readSep 30, 2018
Photo by Fancycrave on Unsplash

Apple adopts a unique way of memory management system called ARC which stands for Automatic Reference Counting. In this article, I will try to summarize iOS memory management including ARC, weak, strong, and unowned keywords.

Note:
I created a simple command line tool project for this article. All sample codes in this article are written inside the main.swift file.
This is just because some part of the codes don’t work properly in the playground, specifically a weak keyword or unowned keyword. It seems like a bug related to the playground.

1. ARC (Automatic Reference Counting)

First of all, as the name implies, ARC only works for reference types; specifically class instances.

Swift uses ARC to keep track and manage your app’s memory. In languages like C, you have to allocate and free an appropriate amount of memory manually when make an object. However in Swift, ARC automatically frees up the memory for class instances when those instances are no longer needed.

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information for you. Inside of the memory, information about the type of the instance and the values of any stored properties associated with that instance are held.

In order to make sure that instances are not deallocated while they are still needed, ARC remembers how many properties, constants and variables are currently referring to the class instance. Otherwise your app is likely to crash.

Strong Reference

In order to prevent instances from being deallocated while they are still needed, property, constant or variable establish a strong reference to the instance when they are assigned an instance.

It’s about time to see how ARC works in action. Let’s take a look at the code below.

class Person {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
deinit {
print("\(firstName) is deallocated.")
}
}
// Initialize an instance
var john: Person? = Person(firstName: "John", lastName: "Lennon")
// Assign the instance to another variables
var john2 = john
var john3 = john
// Set nil to first two instances
john = nil
john2 = nil

There is a simple class called Person which has two properties, init(), and deinit(). The method deinit() will be called only when it is deallocated. After the class definition, an instance of Person class has initialized. There are another two variables which are pointing to the same instance as the first variable.

When you try to deallocate two instances out of those three, nothing happens. Remember there is deinit() method inside of the Person class. Even though you set nil to two instances, deinit() is not called. Why, you ask?

If you pass an instance variable to another variable, those variables refers to exactly the same instance. In other words, they have strong reference to the same instance.

Every class instance has a reference count which is the number of references to the memory for the instance. Since those three variables in the example point to the same instance, the reference count of the instance is three. Thus, even though you set nil to two variables, the reference count is still one. Unless the reference count is not zero, the instance is still alive. That’s why deinit() is not called. If you add “ john3 = nil” at the last of the code, then the instance is deallocated.

Strong Reference Between Classes (Retain Cycle)

class Person {
var firstName: String
var lastName: String
var band: Band?
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
deinit {
print("\(firstName) is deallocated.")
}
}

class Band {
var bandName: String
var drummer: Person?

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

deinit {
print("\(bandName) is deallocated.")
}
}
// Initialize instances of Person and Band
var ringo: Person? = Person(firstName: "Ringo", lastName: "Starr")
var beatles: Band? = Band(bandName: "The Beatles")
// Assign oneself to each other's properties
ringo!.band = beatles
beatles!.drummer = ringo
// Set nil to instances
ringo = nil
beatles = nil

In the example above, there are two simple classes called Person and Band. Each classes has an optional property which has the type of the other class. As you can see in the code, those properties are assigned each other’s instance after its initialization. Current relationship between those two can be described as the picture below.

Relationship between var ringo and var band

What do you think is going to happen if you set those two variables to be nil? That relationship can be described as below.

Relationship after set nil to those two variables

Since variables are set to be nil, strong references between variables and instances are gone. However, instances are still alive because “band” property and “drummer” property have strong references towards each instance. As long as strong references exist, reference count will not be zero. As a result, instances are not going to be deallocated even though variables are nil. This is called memory leak.

“weak”: Avoiding Strong Reference Cycle

Swift provides two ways to avoid strong reference cycle. First one is what is called weak reference.

A weak reference doesn’t have a strong reference to the instance. In other words, it doesn’t increase the strong reference count of the instance. Thus, it is not really in the lifecycle management of the instance if you use the weak keyword. Now let’s modify the previous example a little bit.

class Person {
var firstName: String
var lastName: String
weak var band: Band?
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
deinit {
print("\(firstName) is deallocated.")
}
}

class Band {
var bandName: String
var drummer: Person?

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

deinit {
print("\(bandName) is deallocated.")
}
}
// Initialize instances of Person and Band
var ringo: Person? = Person(firstName: "Ringo", lastName: "Starr")
var beatles: Band? = Band(bandName: "The Beatles")
// Assign oneself to each other's properties
ringo!.band = beatles
beatles!.drummer = ringo
// Set nil to instances
beatles = nil //deinit() is called

Notice Person class’s band property is declared with a weak keyword this time. This change can be described as below.

Relationship between var ringo and var band

This little change makes a big difference. When you set nil to the beatles variable, you can see deinit() method is called if you check the console.

As I mentioned before, a weak reference doesn’t have a strong reference to the instance. Thus, the ringo variable’s band property is automatically set to nil when the beatles variable is deallocated. There is no strong reference remains anymore.

Relationship after set nil to the band variable

“Unowned”: Avoiding Strong Reference Cycle

The second way of avoiding strong reference cycle is using an unowned reference.

Same as a weak reference, an unowned reference doesn’t increase the reference count. Then, what’s the difference between those two? A weak reference is automatically set to be nil by ARC. This means a weak reference has to be an optional type.

On the contrary, an unowned reference is expected to always have a value. Thus, it cannot be declared as an optional type. In other words, an unowned reference is used when the other instance has the same lifetime or a longer lifetime. Let’s see this in an example.

class IDHolder {
let firstName: String
var idCard: IDCard?
init(firstName: String) {
self.firstName = firstName
}
deinit {
print("\(firstName) is deallocated.")
}
}
class IDCard {
unowned let holder: IDHolder

init(holder: IDHolder) {
self.holder = holder
}
deinit {
print("\(holder)'s ID card is deallocated.")
}
}
// initialize IDHolder and set IDCard instance to its idCard property.
var george: IDHolder? = IDHolder(firstName: "George")
george?.idCard = IDCard(holder: george!)
// Set nil to george
george = nil

There are two classes called IDHolder and IDCard. IDCard class has an unowned constant property called holder. Since unowned property cannot exist without the other instance or even be set to nil, IDCard instance is automatically deallocated when george variable is deallocated.

That’s everything for this article. For more detailed information, I recommend that you go to read references of this article. They are all really good articles.

References:

The Swift Programming Language Swift 4.2 — Automatic Reference Counting:
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

Functional swift: All about Closures:
https://medium.com/@abhimuralidharan/functional-swift-all-about-closures-310bc8af31dd

ARC and Memory Management in Swift:
https://www.raywenderlich.com/959-arc-and-memory-management-in-swift

Swift Programming: The Big Nerd Ranch Guide (2nd Edition)
https://www.bignerdranch.com/books/swift-programming/

--

--

Kenta Kodashima

I'm a Software Engineer based in Vancouver. Personal Interests: Books, Philosophy, Piano, Movies, Music, Studio Ghibli.