Memory Management in Swift with ARC

A quick guide to memory management in Swift to improve your app’s performance.

Deeksha Kaul
Level Up Coding

--

Why is the RAM Always Gone? Memory Management | RAM | Memory leaks | Retain cycle | Medium
Memory Management | RAM | Memory leaks

Our device 📴 has two types of memories where it saves our data:

  • Disk memory
  • Random Access Memory (RAM)

When we open an application, a small part of RAM is allocated for that application where all the instances for its classes are stored. This chunk of memory is called Heap memory and this is the part of memory we refer to when we talking about memory management.

Heap is the portion of memory (RAM) where dynamically allocated memory resides

Since the heap memory is very limited, we must manage it properly 💰. Poor management of memory will considerably slow down the app and sooner or later, cause a crash.

But how does swift manage the heap memory?

Swift uses something called Automatic Reference Counting (ARC) for memory management. Every time a new object of a class is created, ARC allocates some amount of memory to store data associated with that object. Each object will have a reference count property that keeps track of everything that has a strong reference to it. Every time a strong reference is created, the reference count of that object gets incremented by one. And whenever a reference goes out of scope, the reference count gets decremented by one.

But how are objects deallocated…

Let us try to understand this with an example. Look at the following Shop class:

Memory management with Swift example — Shop Class

An object will get deallocated if the ViewController it is contained in goes out of scope. However, if the object is created inside a function, its scope will be within the function only. Once the function execution is complete, the object will be deallocated.

Let us assume we have a view controller MyViewController, and we create an object of class Shop in the viewDidLoad() method as follows (let’s assume it is a cake shop 🍰):

Memory management with swift example

In this case, the shop object will not be deallocated as long as MyViewController does not go out of scope. But if we define a function runApp() as follows:

Memory management with swift example

and call runApp() in viewDidLoad() rather than creating the object there, the shop object will be deallocated as soon as runApp() goes out of scope.

Now, reference count, by keeping track of references to the object, identifies whether or not the object is still needed. As soon as the reference count reaches zero, the object will be deallocated and the memory associated with that object will be free to be used by another object 💁.

So how do memory leaks happen?

A memory leak occurs when content remains in memory even after its lifecycle has ended.

In simple terms, a memory leak is a chunk of memory that remains allocated, but never used…🤔…

Let us imagine a situation where two objects only refer to each other and have no other references 🗃 ↔️ 🗃. Since the reference count for both of them cannot be zero, they cannot be deallocated and will continue to occupy memory. This is known as a strong reference cycle or a retain cycle 🔄.

Let us see an example of this. Let us define a class Cake as follows:

Memory management with swift example

Now if we add the following piece of code to runApp():

let cake = Cake(“Sweet Tooth Special Chocolate Truffle Cake”)

This creates an object of Cake. Next, add the attribute ‘cakes’ in Shop.

Memory management with Swift example

Now if we call sells() in runApp(), it will create a retain cycle. This will lead to a memory leak.

Reference cycle example | Memory management | Memory leak | Swift | Medium
Reference cycle in Swift | ARC | Memory leak

Strong references between shop and cake

To break this cycle, we use weak and unowned references.

Weak? Unowned? 🤯

To break the retain cycles we make use of weak references. By default, all references in swift are strong and increment the reference count by 1 upon creation. But weak references don’t affect the reference count. Also, weak references are always optional variables and when the reference count becomes zero, weak reference gets set to nil.

Break the retain cycle example | Memory management | Swift | Medium
Weak reference to avoid memory leak | Breaking the retain cycle

Weak reference from cake to shop

So if we go back to our example, and in Cake class, change the declaration of ‘soldBy’ to,

weak var soldBy: Shop?

when sells() is called in runApp(), reference count to shop does not increment. So when all other references to shop() are removed, it gets deallocated and reference count for cake becomes zero.

Similar to weak references, unowned references do not impact the reference count of an object. But unlike a weak reference, unowned references are never optional. This means if you try to access an unowned property that points to an object that has already been deallocated, it is like forcefully unwrapping an optional value that is nil.

It is important that an unowned reference should only be used if the reference and the object are sure to get deallocated at the same time.

So wait… are weak and unowned references the same other than the fact that one is optional and the other isn’t… 🤔.

Well, not really… When we talk about reference count, we usually refer to the strong reference count of the object. Similarly, swift maintains an unowned reference count and weak reference counts for the object. But what really sets weak reference apart from unowned and strong is that weak reference points to something known as a “side table” rather than the object itself. This is why, when a weak reference goes out of scope the object can be de-initialized and deallocated… Since weak reference doesn’t point to the object at all. When the strong reference count reaches zero, the object gets deallocated, but if the unowned reference count is more than zero, it leaves behind a dangling pointer.

The side table is a separate chunk of memory that stores the object’s additional information. An object initially doesn’t have a side table entry, it is automatically created when a weak reference is created for the object.

Reference cycles with closures

Closures are another way of ending up in reference cycles leading to memory leaks. Let us understand this with our example of Shop class. Let us add a computed property cakeCount to our Shop class.

Reference cycle example

If we use this property in runApp(), a shop object refers to the closure through the computed property cakeCount and the closure refers to the object through “self”. Since this a strong reference both ways, it creates a retain cycle.

In this case, we use Capture Lists to capture a weak or unowned reference to self in the closure as follows:

Capture List to avoid memory leaks example

Now the closure does not impact the reference count and as soon as shop goes out of scope, so does the closure.

And that is a wrap. This covers the basics of memory management in Swift. Thanks for reading! Any questions are welcome and feedback is very much appreciated, so please drop a comment!

--

--