Swift Memory Management – Automatic Reference Counting

Filed Under: Swift
swift-arc-strong-references-illustration

In this tutorial, we’ll be covering Swift memory management and learn about Automatic Reference Counting (ARC) in Swift. It’s an essential concept and is useful in preventing memory leaks in our iOS Applications.

We’ll be covering the following concepts:

  • What is ARC and how does it work?
  • Resolving Strong Reference Cycles
  • Differences between strong, weak and unowned references
  • Strong Reference Cycles in Closures
  • Resolving Reference Cycles in Closures using Capture Lists

Swift Memory Management – Automatic Reference Counting

Automatic Reference Counting is an underlying tool that takes care of allocating and freeing memory for references. It keeps a track of the count of references for an object.

How does it allocate?
When an object is initialised, ARC assigns it a chunk of memory. That memory holds information about that object, such as its properties, constants, references it is linked to. Further reading – swift init function.

How does it deallocate?
ARC keeps a count of the number of references to the object. It cannot deallocate the object when there are references since that might lead to runtime crashes. Once the reference count is 0, it deallocates the object by calling the deinit() and frees up the memory.

Note: ARC and memory management do not guarantee to work perfectly in XCode Playgrounds as they do in XCode projects. Nevertheless, since the focus of this post is ARC and not iOS Applications, we’re using playgrounds below, just for the sake of convenience.

Swift ARC in Action

Let’s create a swift class and then instantiate it.


import Foundation

class Student {
    let name: String
    var website  = "JournalDev"
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
        print("\(website) is being deinitialized")
    }
}

var s : Student? = Student(name: "Anupam")
var reference = s //reference count is 2
var reference2 = reference //reference count is 3
//prints
//Anupam is being initialized

To deallocate the object, we need to remove the references.


reference = nil
s = nil
reference2 = nil
//prints
//Anupam is being deinitialized
//JournalDev is being deinitialized

In the above code, deinit block is run automatically by the ARC. You cannot call it manually.
We’ve created references of type Optional so that if they’re invoked in the future, it won’t cause a crash.
Setting the references as nil lets the ARC change the reference count.

Note: ARC is NOT a garbage collector. ARC can only clear memory if it unused. In some cases the programmer needs to help ARC to clear the memory and prevent memory leaks as we shall see in the next section.

Resolving Strong Reference Cycles

By default, the ARC creates a strong reference. Now we can have scenarios where there are reference cycles as shown in the code below.


class Student {
    let name: String
    init(name: String) { self.name = name }
    var university: University?
    deinit { print("\(name) is being deinitialized") }
}

class University {
    let uniName: String
    init(uniName: String) { self.uniName = uniName }
    var student: Student?
    deinit { print("University \(unit) is being deinitialized") }
}

Each of the classes has a reference to the other. Let’s initialise them.


var me: Student?
var uni: University?

me = Student(name: "John Doe")
uni = University(uniName: "IIT")

me?.university = uni
uni?.student = me

This creates a strong reference between both the classes. Something that the below illustration rightly depicts.

swift memory management, swift Automatic Reference Counting strong references

Let’s see how the strong references impact the lifecycle of the objects.


me=nil
uni=nil

//Does nothing

We’ve set the references to nil, to let ARC deallocate them but it doesn’t happen.
Strong Reference Cycle is created in the above code that is a potential cause of memory leaks!

Before we get into resolving the strong references let’s look at other types of references.

strong, weak, unowned references

  • strong references are the standard type of references created by default.
  • weak references allow instance creations but do not add up in the reference count of the ARC. Typically a reference should be marked as weak when it has a shorter lifetime than the other reference.
  • unowned references are similar to weak references except that they’re used on references that have a longer lifetime than the other reference.
  • An unowned reference should be used only when you’re absolutely sure that the reference does exist there otherwise if it’s invoked on a nil reference it’ll crash.
  • A weak reference can be used when the reference is nil.

Resolving Strong Reference Cycles

We can resolve strong reference cycles in the above example by making any one of the references as weak or unowned references. Such references do not add to the reference count in the ARC.


class Student {
    let name: String
    init(name: String) { self.name = name }
    var university: University?
    deinit { print("\(name) is being deinitialized") }
}

class University {
    let uniName: String
    init(uniName: String) { self.uniName = uniName }
    weak var student: Student?
    deinit { print("University \(uniName) is being deinitialized") }
}

Initialising the references and then setting then nil would now call the deinit statement.


var me: Student?
var uni: University?

me = Student(name: "John Doe")
uni = University(uniName: "IIT")

me?.university = uni
uni?.student = me

me=nil
uni=nil

//prints
//John Doe is being deinitialized
//University IIT is being deinitialized

Note: Setting unowned reference in the above example would cause a crash since unowned reference cannot be set to nil.

swift memory management, swift Automatic Reference Counting strong references weak references

Resolving Reference Cycles in Closures using Capture Lists

Closures are reference types. Hence they can have a strong reference cycle created as shown below:


class User {
    
    let name: String
    let skill: String
    
    lazy var summary: () -> String = {
        return "\(self.name) (\(self.skill))"
    }
    
    init(name: String, skill: String) {
        self.name = name
        self.skill = skill
    }
    
    deinit {
        print("Deallocated User")
    }
    
}

Let’s allocate and deallocate an object.


var name: User? = User(name: "Anupam", skill: "Swift")

name?.summary()

name = nil

//deinit NOT called.

Because closures are reference types they create a strong reference cycle by capturing the self reference. Hence the reference count can never be zero.
Let’s try resolving this.

Capturing Lists

We can use self as a weak or unowned reference inside the closure as shown below.


lazy var summary: () -> String = {[unowned self] in
        return "\(self.name) (\(self.skill))"
    }

We explcitly pass a list passing an unowned reference of self.

As we known from Closures, the parameters are separated from the return type by the in keyword.
With that the strong reference cycle in closures is resolved.

That’s all for quick roundup on swift memory management and automatic resource counting feature.

Reference: Apple Docs

Comments

  1. Bradley says:

    Hi, Thanks for the post, can you explain why you are using unowned instead of weak in the last example for closures. Thanks in advance

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages