Memory management in Swift: Strong Reference & Retain Cycle

Swift uses ARC (Automatic Reference Counting) similar to Objective-C to track and manage application memory. In most cases, we don’t need to bother about memory management by ourselves, the swift compiler will take care of it. But there are some cases where we need to deal with it  by ourselves. I am going to discuss some of the common cases from those.

Let’s imagine we have two classes Person and Apartment:

class Person {
let name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let number: Int
init(number: Int) {
self.number = number
}
deinit {
print("Apartment \(number) is being deinitialized")
}
}
var person: Person? = Person(name: "John Doe")
person = nil
var apartment: Apartment? = Apartment(number: 123)
apartment = nil
// output:
// John Doe is being deinitialized
// Apartment 123 is being deinitialized
view raw RetainCycle1.swift hosted with ❤ by GitHub

The deinit method is called when a class is deallocated and set to nil. Now so far so good, we have our code clean and leak free as both deinit methods are being called.

ARC tracks the number of references to a object created and deallocates the instance when no longer needed. But it is possible to achieve a situation where an instance of a class never reaches to a point where it has no strong references.

Consider the following change:

class Person {
let name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let number: Int
var person: Person?
init(number: Int) {
self.number = number
}
deinit {
print("Apartment \(number) is being deinitialized")
}
}
var person: Person? = Person(name: "John Doe")
var apartment: Apartment? = Apartment(number: 123)
apartment.person = person
person = nil
// output:
//
view raw RetainCycle2.swift hosted with ❤ by GitHub

We can see the deinit is not getting called anymore, i.e. the person instance is not getting rid of the memory completely. Somewhere someone is holding a strong reference to it. This is happening because we are holding a strong reference of the Person class instance in the Apartment class instance. Now if we change the reference from strong to weak:

class Person {
...
}
class Apartment {
...
weak var person: Person?
...
}
var person: Person? = Person(name: "John Doe")
var apartment: Apartment? = Apartment(number: 123)
apartment.person = person
person = nil
// output:
// John Doe is being deinitialized
view raw RetainCycle3.swift hosted with ❤ by GitHub

We magically start to get our output again! Now again, if we modify our classes like this:

class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let number: Int
var person: Person?
init(number: Int) {
self.number = number
}
deinit {
print("Apartment \(number) is being deinitialized")
}
}
view raw RetainCycle4.swift hosted with ❤ by GitHub

We have both classes referring to each other as strong reference. If two class instances hold a strong reference to each other, such that each instance keeps the other one alive, these instances never reaches a zero(0) reference situation. This is known as a strong reference cycle/ retain cycle.

If we run the following code block, it never reaches the deinit method again.

var person: Person? = Person(name: "John Doe")
var apartment: Apartment? = Apartment(number: 123)
person?.apartment = apartment
apartment?.person = person
person = nil
apartment = nil
// output:
//
view raw RetainCycle5.swift hosted with ❤ by GitHub

The retain cycle causes both object instances to live in memory and no way to free them and ultimately causing a memory leak.

We resolve strong reference cycles by defining some of the relationships between classes as weak or unowned reference instead of as strong reference.

class Person {
...
}
class Apartment {
...
weak var person: Person?
...
}
var person: Person? = Person(name: "John Doe")
var apartment: Apartment? = Apartment(number: 123)
person?.apartment = apartment
apartment?.person = person
person = nil
apartment = nil
// output:
// John Doe is being deinitialized
// Apartment 123 is being deinitialized
view raw RetainCycle6.swift hosted with ❤ by GitHub

By defining the person variable in Apartment class as weak, we’re stopping the increment on person vars reference count and thus breaking the retain cycle. (I’ll discuss more on weak and unowned in a future blogpost.)

A strong reference cycle can also occur if a closure variable calls its class instance inside the closure body. Consider the following example:

class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
return "<\(self.name)>\(self.text)</\(self.name)>"
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("HTMLElement \(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
paragraph?.asHTML()
paragraph = nil
// Output:
//
view raw RetainCycle7.swift hosted with ❤ by GitHub

The asHTML property is declared as a lazy property, because it is only needed when if and only if the element actually needs to be rendered as a string value for some HTML output target; thus lazily initialized. The fact asHTML is a lazy property means we can refer to self within the default closure, because the lazy property will not be accessed until the initialization has been completed and self is known to exist.

If we run the code, we’ll see the deinit method is not being called, again! The instance’s asHTML property holds a strong reference to its closure. However, because the closure refers to self within its body (as a way to reference self.name and self.text), the closure captures self, which means, it holds a strong reference back to the HTMLElement instance and thus a strong reference cycle is created between the two.

We can resolve this strong reference cycle between the closure and the class instance by defining a capture list as part of the closure’s definition.

A capture list defines the rules to use when capturing one or more reference types within the closure’s body.

As with strong reference cycles between two class instances, we declare each captured reference to be a weak or unowned reference rather than a strong reference. The appropriate choice of weak or unowned depends on the relationships between the different parts of the code.

This implementation of HTMLElement is identical to the previous implementation, apart from the addition of a capture list within the asHTML closure. In this case, the capture list is [weak self], which means “capture self as an weak reference rather than a strong reference”.

class HTMLElement {
...
lazy var asHTML: () -> String = { [weak self] in
guard let htmlElement = self else { return "" }
return "<\(htmlElement.name)>\(htmlElement.text)</\(htmlElement.name)>"
}
...
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
paragraph?.asHTML()
paragraph = nil
// Output:
// HTMLElement p is being deinitialized
view raw RetainCycle8.swift hosted with ❤ by GitHub

This time, the capture of self by the closure is an weak reference, and does not keep a strong hold on the HTMLElement instance it has captured. If we set the strong reference from the paragraph variable to nil, the HTMLElement instance is deallocated, as can be seen from the printing of its deinitializer message in the example above.

A more detailed example and explanation can be found in Apple’s Swift 3 Documentation. All the examples used here are taken from the documentation and been simplified for convenience.

Happy coding 🙂

Leave a Reply