Strong Reference cycle

Strong Reference Cycles, forming and deforming



This blog post suppose to be a closure talk, specially on Capture List. But we just can not dive on that yet. Because we have not talked about the Strong Reference Cycles. So this blog post is dedicated on Strong Reference Cycles. Just a reminder, we need to spend some good time on this blog post, but eventually it will pay off. So let us dive deep.

Background

Strong Reference Cycles

A Strong Reference Cycles occurs among two reference type instance, when they holds a strong reference of other instance and for that reason ARC can not release any of them. Causing a deadlock.

Q/A
What is ARC?
Seriously!!! ARC stands for Automatic Reference Counting. ARC is the memory manager to alloc and dealloc the Reference type instance.

Why ARC will not release those instance?
AS ARC release and purse the space of an instance only and only if, that instance has a reference count of zero. So if there is no one holding a reference of the instance, then that instance have a zero reference count and immediately ARC will purse the space of that instance by releasing it.
When two reference typed instances create a Strong Reference Cycles then none of the instances have a zero reference count. Because they are referencing each other strongly. So ARC will not release any of those two instance as both have a reference count more than zero.

Forming Strong Reference Cycles

To form a Strong Reference Cycles both the instances should be reference typed instance.

What is Reference typed instance?
Oh Come on!
When an instance is pass around on the program, using it’s reference only; then that instance is a reference typed instance.
What does pass around means? Means copying the instance or passing the instance as an argument of a function.
What does Reference only means? Means the underlying data is same for two instance as they shared the same reference when a copy is made. Changing one will update the other.

So Who are the Reference type on Swift? Well two only:

  • class
  • closure

And definitely we know function is a special type of closure. So yep function is also reference type.

On generally, two class instances or a class instance with a closure can generate the strong reference cycles. We will talk about the closure part on the next talk.

Let us go through an example to clarify the strong reference cycles for two class instance. Here we will consider two class to construct the situation:


class Jedi{
    let name: String
    var weapon: Lightsaber?
    
    init(name: String) {
        self.name = name
        "\(name) is a Jedi now"
    }
    
    deinit {
        "\(name) became one with the Force"
    }
}

class Lightsaber{
    var owner: Jedi?
    let type: String
    let model: String
    
    init(type: String, model: String) {
        self.type = type
        self.model = model
        "The \(type) \(model) lightsaber is formed"
    }
    
    deinit {
        "\(owner?.name ?? "") \(type) \(model) lightsaber is destroyed"
    }
}

Interesting right 😎? We have two class Jedi and Lightsaber. They both have an optional instance of other class. Once a class is release the corresponding deinit will be called.


var lightsaber: Lightsaber?
var yoda: Jedi?

On the above we have one optional Jedi and one optional Lightsaber. Why we used optional? Because we want to set them to nil to see if the deinit was called for that corresponding class.

Let us initiate lightsaber.


lightsaber = Lightsaber(type: "single blade", model: "Shoto")

So the lightsaber will hold the reference of the newly created Lightsaber instance

For yoda:


yoda = Jedi(name: "Yoda")

Now let us assign the owner of lightsaber with yoda. Similarly weapon of yoda with lightsaber.


yoda?.weapon = lightsaber
lightsaber?.owner = yoda

Describe the situation through some images, We should. [Yoda style 😉]

Strong Reference cycle

As the class is reference based type. Means when assigning/ copying; the reference is shared, not a new instance is created. So the weapon of yoda and lightsaber is the same instance. Same goes with the owner of lightsaber with yoda.

Now the interesting part is setting nil to yoda and lightsaber. When we set nil to yoda and lightsaber, these two instance will loose the reference of the corresponding instance.


yoda = nil
lightsaber = nil
Strong Reference cycle

Well well the deinit was not called, the corresponding text was not printed. What happened?

Obviously we set nil to lightsaber and yoda. That’s why they loose the reference of the corresponding instance,Lightsaber instance and Jedi instance.

But those two instance,Lightsaber instance and Jedi instance, still have the reference of each other, causing a strong reference cycles.

The ARC is unable to release any one of those, because they both still have a reference count of 1.

Q/A:
What is the after product of strong reference cycles?
well memory leak.
How?
We will have initialized memory space and after setting the nil, we will not have the reference of that memory space. So this memory space can not be traced and can not be purged. Causing a leak on memory space.

How to break Strong reference cycles?

Well simple, set nil to either of the weapon of yoda or owner of lightsaber to nil before setting nil to yoda and lightsaber. And voila, the deinit is called. The corresponding text is printed.


yoda?.weapon = nil
//lightsaber?.owner = nil

yoda = nil // "Yoda became one with the Force"
lightsaber = nil //" single blade Shoto lightsaber is destroyed"

What we did in the previous code block is that, we set nil to a strong reference prior to setting nil to the instance. And thats works great for us.

But this manual solution is not a smart one. Swift provides some cool solution for breaking down the strong reference cycles.

Dissection of unowned and weak

Swift provides two way for breaking the strong reference cycles, unowned and weak. Both have the same target of providing weaker reference, so that ARC can purge the space when not needed without causing any memory leak.

Q/A
What will happen on Reference count when we use a weaker reference, does it increment as strong does?
No, weaker reference does not increase the reference count of that instance.

weak and unowned actually tells the compiler about the relationship between two reference type instance.
When we define a variable/constance on Swift, it is by default Strong reference. So if we say let jedi = Jedi(name: "Obi-Wan Kenobi") then the jedi is a strong reference.
But when we use weak or unowned then the reference will be weaker reference. So if we say unowned let jedi = Jedi(name: "Obi-Wan Kenobi") or weak let jedi = Jedi(name: "Obi-Wan Kenobi") the the jedi reference is a weaker reference and ARC can purge the memory space.

Q/A if both does the same work then why two? Why not one?

Aha, right. So now we have to talk about the usage and property of weak and unowned. That how we will have a clear idea of why there is two solution.

weak

The weak keyword is used when the other instance has a shorter life span among two. An example will help:


class JediMaster{
    let name: String
    weak var weapon: PlasmaLightsaber?
    
    init(name: String) {
        self.name = name
        "\(name) is a Jedi Master now"
    }
    
    deinit {
        "\(name) became one with the Force"
    }
}

As we can see the var weapon of JediMaster is a weak reference. As we are thinking the PlasmaLightsaber has a shorter life span than the JediMaster.

Q/A
How can we think that PlasmaLightsaber has a shorter life span?
Hmm think like this, no JediMaster then his PlasmaLightsaber will not be his.
Ok ya… let us face it. There is no way we can say PlasmaLightsaber has a shorter life span than JediMaster. We are just assuming and probably it should. But we can not be certain. Situation of this type often pops up from nowhere on real world. We will continue the discussion on the best practice to follow section.

Q/A
Why weak has an optional type?
Ya a very good question. As we said, the weak variable is expected to have a shorter life span and the compiler knows it. So when ever possible the compiler will set nil to the weak instance. That is why Swift provide us weak as optional type, so that we can use optional chaining to access the instance’s value without break/crashing the app.

Q/A
Can weak be a let rather than a var?
Another good question. Well, it can’t. As because the compiler will set nil to the weak instance at some later point, so its’ value will definitely change. So no let for weak, only var is real for weak 🤓.

Any more question? OK just hold it for some time. Let us continue the example.


class PlasmaLightsaber{
    let type: String
    var owner: JediMaster?
    
    init(type: String) {
        self.type = type
        "The \(type) lightsaber is formed"
    }
    
    deinit {
        "\(owner?.name ?? "") \(type) lightsaber is destroyed"
    }
}

Above is the PlasmaLightsaber, self explanatory.

Now let us initiate and assign the properties to the corresponding instances.


var obwan: JediMaster? = JediMaster(name: "Obi-Wan Kenobi")
var obwanLightsaber: PlasmaLightsaber? = PlasmaLightsaber(type: "single blade")

obwan?.weapon = obwanLightsaber
obwanLightsaber?.owner = obwan

We can see the init massage been printed. This time the PlasmaLightsaber instance of JediMaster is a weak reference. Pictorial view as follows:

Now the most important thing, deinitializing the instances. First set nil to obwan.


obwan = nil

Setting nil to obwan will remove the ref of JediMaster instance both from the obwan and from PlasmaLightsaber instance, as we have a weak reference system working here. ARC do not increase the reference count of a weak reference. Pictorial:

Then set nil to obwanLightsaber.


obwanLightsaber = nil

And we saw the two deinit method been called. So Pictorial view as follows:

Hmmm 😌. Now we have some proper releasing of instance, no memory leaks.

unowned

So now let us have a talk on unowned. When the other instance has a same or longer life span then unowned is used. For this example we will again use the Jedi concepts. This time Jedi Youngling and Jedi knight. Jedi Knights can take an Jedi Youngling as apprenticed. So we can write as following Knight class.


class JediKnights{
    let name: String
    var apprenticed: JediYoungling?
    
    init(name: String) {
        self.name = name
        "\(name) is a JediKnights now"
    }
    
    deinit {
        "\(name) became one with the Force"
    }
}

A JediKnights may or may not have an apprenticed. On the other hand a Jedi Youngling will have a master. For this example let us assume that the master is a JediKnights and the JediYoungling should have a master.


class JediYoungling{
    let name: String
    unowned let master: JediKnights
    
    init(name: String, master: JediKnights) {
        self.name = name
        self.master = master
        "JediKinghts, \(master.name), took \(name) as a apprentice"
    }
    
    deinit {
        "\(name) is no more a JediYoungling"
    }
}

Again we have a cyclic reference here of two reference type instance, apprenticed: JediYoungling? and master: JediKnights. But this time we are using the unowned solution to break the Strong Reference Cycle.

Q/A
Why the unowned reference is a constance, means why it is using let?
Basically the unowned reference theoretically should be a same or longer lived instance. As a result an unowned instance can not be set as nil. So always an unowned instance is initialized on the class‘s init method. Thats why an unowned instance is a constant.

Q/A
If the compiler do not set nil to an unowned instance, then how the strong reference cycle is broken using the unowned?
We already answered that question in different words. unowned stablished a weaker reference. So the ARC do not increment the reference count on an unowned instance. As a result on later time ARC can purge the space of the unowned instance.

Q/A
Why the type of a unowned is non-optional?
Well it need not to be an optional as ARC will not set nil to an unowned instance. So it will always have value.

Back to our unowned talk. So let us initiate the instance and assign the apprenticed property of JediKnights to created a strong reference, as we already assign the master property of JediYoungling.


var master: JediKnights? = JediKnights(name: "Obi-Wan Kenobi")
var youngling: JediYoungling? = JediYoungling(name: "Anakin Skywalker", master: master!)

master?.apprenticed = youngling

So now what will happen if we nil to those instance.


master = nil
youngling = nil

As we used the unowned version of JediKnights it becomes a weaker reference. So ARC do not increase the count for master: JediKnights. As a result setting nil will remove the instances properly without any memory leak.

Best practice to follow

The best practice is relative, hugely depends on the problem domain. But there should be one good practice to follow when choosing weak and unowned for breaking the Strong Reference cycle.
Always prefer weak over unowned.
Why?
Because a weak reference will produce an optional variable, like weak var weapon: PlasmaLightsaber?. which can be:

  • safely used through optional chaining, obwan?.weapon?.type.
  • checked for the possible nil value.

But on the other hand as unowned is non-optional value and also ARC treat it as a weaker reference so there is the possibility of purging the memory space of that unowned variable by the ARC and there will be no safer way of checking that.
So we are playing a very risky game of putting unowned solution for breaking the Strong Reference Cycle. Suddenly you will see a lot of crash report of using the unowned reference.

Now we can make the decision.

End Talk

All the source code for this blog post is available on GitHub.

Well this blog post is already a bit long. We will cut it here as we will talk about the Strong Reference Cycles on Closure on the next talk. Till then Happy talking.

One thought on “Strong Reference Cycles, forming and deforming

Leave a Reply

Notifications for mobidevtalk! Cool ;) :( not cool