top of page

ARC and Retain cycle


A puzzle

ARC a.k.a Automatic Reference Counting is specifically designed to manage our app’s memory usage. It’s pretty intimidating at first but when you get the general idea behind it, you’ll realise it’s a blessing.

As per Swift docs —

Memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed

Basics

The key thing to remember here is that ARC only works for instances of reference types, i.e. classes and not for value types such as structs and enums.

Why so?

TL;DR — Classes work with references to object while Structs and Enums work with copies of the object

Long story — In case of reference types, when you assign one object to another reference, what happens behind the picture is , a new reference starts pointing to that object , so, we are actually increasing the reference count. However, in case of value types, when we do the same operation, instead of the new reference pointing to the object, we get a completely separate copy of that object to work with. So we are not increasing the reference count and thus ARC does not comes into play.

How it works

  • Whenever we create an instance of a class, ARC allocates some memory to store data, such as stored properties, related to that instance

  • Now, when this instance outlives it’s scope, ARC frees up the memory by deallocating that instance

  • If we were to access that instance, our app will crash and burn (or just the first one!)


Example of how ARC works
Like this

do block above defines a scope for all the variables that are declared inside of it . At the end of do block, the scope expires and all the variables are deallocated. As we can see, as the scope of employeeObj expires, ARC deallocates memory associated with it and calls the deinit .

How does this, ARC, decides the life scope of an instance ?

Whenever we create an instance, without any explicit reference specifier, such as this —

it is considered a Strong reference .

Now, as a thumb rule for ARC, as long as there is at least one strong reference to an object, ARC will not de-allocate that instance. It’s as simple as that !

What are types of references other than Strong ?

weak and unowned ! They are essentially the same but only differ in the fact that weak returns an optional reference, so it’s safe to unwrap and use it while unowned returns a concrete (or not an optional) reference, which, if turns out to be nil, will crash the app.

What is the basic difference between strong and weak ?
  • Each strong reference increases the reference count of an instance by 1 whereas weak reference does not increase the reference count.

  • strong reference can lead to retain cycle which in-turn leads to memory leaks whereas weak reference can be used to avoid retain cycles

  • strong reference can never be nil during the course of its life whereas weak reference can be nil

Now hold on just a minute ! WHAT IS RETAIN CYCLE ?

Good question. Call it whatever you want ! Headache. Curse. Nightmare.

Retain cycle a.k.a Strong reference cycle

If you are working with classes and you haven’t paid close attention to the code you’re writing, chances are you already have retain cycles inside your app.

Even protocols that act as delegates can cause retain cycle as well.

If all these create a retain cycle, why don’t we just go for value types instead ?

Yes, we can go for value types like struct instead of class but what if we need some sort of functionality that is only provided by classes, say, inheritance !

Let me demonstrate what a retain cycle looks like —

Let’s say we work in a company where each employee owns a laptop and vice versa:

The code is simple. Each employee has a laptop and each laptop belongs to an employee.

The usual output should say that we would have our employee and laptop instances allocated memory and after the do scope expires, these instances should be deallocated. Right? Well, not in this case. See for yourself !


Retain cycle caused
Oops ?
Where are all those deinit print statements ?

Turns out the instances were never deallocated.

That’s not because the scope never expired, it did ! The problem arises due to this line —

Due to this line, the instance’s internal stored properties laptop (for class Employee ) and employee (for class Laptop ) are now pointing to each other or referencing each other although the instance’s original reference( employeeObj and laptopObj ) has been removed. And since these stored properties are strong, ARC will not deallocate them as they increase the reference count. This is called as Retain Cycle.

The way to break this retain cycle is to mark the stored property as weak . This will not increase the reference count for that instance and thus that instance will be deallocated.

We can mark either one of those stored properties as weak and it will do the trick. Let’s say we mark the laptop property inside Employee class as weak :

The output will now be changed to —


Retain cycle resolved
Ta-da!

If you think about it, weak is a win-win situation because it avoids retain cycleand memory leaks. So the obvious question that will come your mind is

Why not always use weak ?
  1. Overusing weak in every closure introduces unnecessary handling of optionals (created by weak reference) via optional chaining or unwrapping

  2. Non escaping closures (like higher order functions) doesn’t need weak reference in most of the cases

If you want to learn more on this particular topic, do visit this article

Related Posts

Comentarios


bottom of page