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
- What is
Strong reference cycles
? - How
Strong reference cycles
occurs? - How to break
Strong reference cycles
? - Dissection of
unowned
andweak
- weak
- unowned
- Best practice to follow
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 😉]
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
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”