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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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") } } |
1 2 3 4 5 6 7 |
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.
1 2 3 4 5 6 7 8 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.
1 2 3 4 5 6 7 8 |
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.
Let’s see how the strong references impact the lifecycle of the objects.
1 2 3 4 5 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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.
Resolving Reference Cycles in Closures using Capture Lists
Closures are reference types. Hence they can have a strong reference cycle created as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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.
1 2 3 4 5 6 |
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.
1 2 3 4 5 |
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