Behind all the coding that we are doing, you probably have noticed some of your variables with the reference of strong
, weak
or unowned
when writing your codes. What do they really mean? Does it make your variable stronger by declaring all with the reference of strong?
The usage of strong
, weak
or unowned
are actually related to the memory management in Swift called Automatic Reference Counting (ARC). Let’s slow down a little and try to understand what everything means here. So, ARC actually does automatic reference counting. In the definition of Computer Science, Reference Counting is a technique of storing number of references, pointers, or handles into a resources such as an object, block or memory, disk space or other resources. In short, ARC actually helps store references into memory and helps clean up when it is not being used.
On a side note, reference counting in this case only applies to instance of classes and not structures and enumerations as they are both value types and not reference types.
Before we go further, why is memory management such a big deal? It is indeed a big deal as memory management plays a huge role in allocating memory so that the program can perform at the request of the user and could be free for reuse when no longer needed.
But what could really happen if you exhaust the memory?
- The task will stop performing meaning you won’t be able to execute any of your task.
- The task probably will not progress but continue running and running until it hits the limit and the program crashed.
- You probably don’t want the user to use a buggy program.
What is Automatic Reference Counting (ARC)?
As mentioned in the official documentation,
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.
ARC also keeps track of information such as knowing the relationship between the code and because of that, ARC is able to manage the memory resource effectively.
How does ARC work?
Each time you create a class instance through init()
, ARC automatically allocates some memory to store the information. To be more specific, that chunk of memory holds the instance, together with the values of the properties. When the instance is no longer needed, deinit()
will be called and ARC will free the memory space of that instance.
With the code below, it is pretty self explanatory but I still want you to understand what the code does. This is an example of two classes with Person
instance and Gadget
instance which has an init
method where it will set the instance’s property meaning allocate whatever information into the memory. Also, with deinit
where we will see the instance being deallocated meaning memory containing the information will free up in our case.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
var gadget: Gadget?
deinit {
print("\(name) is being deinitialized")
}
}
class Gadget {
let model: String
init(model: String) {
self.model = model
print("\(model) is being initialized")
}
var owner: Person?
deinit {
print("\(model) is being deinitialized")
}
}
This is an example of a class called Person
and the instance has a name
property with a type of String
. The Person
class has an init
where it will set the instance’s name
property meaning we will allocate whatever information into the memory. In the following code, there is a deinit
where we will see the instance being deallocated meaning memory containing the information will free up in our case. And, the Person
instance has a gadget
property of type String
and an optional Gadget
because a person may not have a gadget. And the same theory applies with the Gadget
class.
The Difference between Strong, Weak and Unowned Reference
If you are more of visual person, the image below basically explains the requirement of using each of the reference.
Strong vs Weak vs Unowned – Quick Facts
- Usually, when a property is being created, the reference is strong unless they are declared
weak
orunowned
. - With the property labelled as `weak`, it will not increment the reference count
- An `unowned` reference falls in between, they are neither strong nor or type optional. Compiler will assume that object is not deallocated as the reference itself remain allocated.
Strong reference
Let’s take a look at the following example. We have a variable of Person
type with the reference of “Kelvin” and a variable of Gadget
type with the reference of “iPhone 8 Plus”.
var kelvin: Person?
var iphone: Gadget?
kelvin = Person(name: "Kelvin")
iphone = Gadget(model: "iPhone 8 Plus")
Now if you set both variables to nil
and run the code in Playgrounds like this:
Take a look at the console message. You should see both variables are deinitialized properly.
Now add the following code to assign kelvin
with an iphone
and set the owner
of the iphone
. Make sure you insert the code before setting kelvin
and iphone
to nil
:
kelvin!.gadget = iphone
iphone!.owner = kelvin
This is an example of how a strong reference will look like when we link the two instances together. Remember that in the Person
class that there is an additional variable of gadget
, here we are giving it a value of iphone
variable. Visually looking, this is what your code actually looks like.
Okay, run the code again in Playgrounds. You should notice that the console only shows you messages related to initialization.
Obviously, when we break the strong reference, the reference count did not drop to zero and the instances were not deallocated by ARC. Why? Let me illustrate the issue visually. The strong reference between the Person
instance and the Gadget
instance remain and cannot be broken even both kelvin
and iphone
were set to nil
.
This is what we call strong reference cycle, leading to memory leaks in your apps. To break the strong reference cycle and prevent memory leaks, you will need to use weak
and unowned
references.
Weak reference
Weak reference are always declared as optional types because the value of the variable can be set to nil
. In order to indicate a reference as weak, you simply add weak
keyword before the property or variable declaration like this:
weak var owner: Person?
ARC automatically sets weak reference to nil
when the instance is deallocated. And because of the change of value, we know that variable will need to be used here as constants will not let you change the value.
We can break the strong reference cycle by using weak references. A weak reference does not keep a strong hold on the instance. With the weak reference, we can resolve the memory leak issue as illustrated in the previous section. We will modify the Gadget
class and declare owner
as weak reference like this:
class Gadget {
let model: String
init(model: String) {
self.model = model
print("\(model) is being initialized")
}
weak var owner: Person?
deinit {
print("\(model) is being deinitialized")
}
}
Now re-run the code in Playgrounds and see what will happen.
As you notice the console message, both variables are now deallocated properly. After changing the owner
variable to weak
, the relationship between two instances looks a little different from the previous one:
With the weak
reference, when you set kelvin
to nil
, the variable can be deallocated properly because there is no more strong reference pointing to the Person
instance.
Unowned reference
An unowned reference is very similar to a weak reference that it can be used to resolve the strong reference cycle. The big difference is that an unowned reference always have a value. ARC will not set unowned reference’s value to nil
. In other words, the reference is declared as non-optional types.
Use an unowned reference only when you are sure that the reference always refers to an instance that has not been deallocated. If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.
Since an unowned reference cannot be an optional, we will modify the sample code a bit:
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
var gadget: Gadget?
deinit {
print("\(name) is being deinitialized")
}
}
class Gadget {
let model: String
unowned var owner: Person
init(model: String, owner: Person) {
self.model = model
self.owner = owner
print("\(model) is being initialized")
}
deinit {
print("\(model) is being deinitialized")
}
}
As you can see, the owner
variable of Gadget
is now defined as a non-optional variable and an unowned
reference. Because of this, the initializer is modified to accept the owner
as a parameter. The relationship between Person
and Gadget
is slightly difference here, if you compare it with the example of the weak reference. Here, a Person
may own a gadget but a Gadget
must have a corresponding owner.
Now let’s see how the allocation and deallocation of the instances work. Declare a variable of kelvin
of Person
type which is optional.
var kelvin: Person?
Next, create a Person
instance and assign a Gadget
instance as person’s gadget
property.
kelvin = Person(name: "Kelvin")
kelvin!.gadget = Gadget(model: "iPhone 8 Plus", owner: kelvin!)
Here’s how the reference looks like with the two instances linked up together. The Person
instance has a strong reference to the Gadget
instance with the Gadget
instance having an unowned reference pointed to the Person
instance.
Let’s try breaking the strong reference by declaring kelvin
variable to nil
and see what happens.
kelvin = nil
Run the code in Playgrounds and here is result:
As you can see, the instances of Person
and Gadget
are deallocated properly. Since we broken the strong reference of the Person
instance (i.e. kelvin
), the instance is deallocated. Consequently, as there is no strong reference to the Gadget
instance, it is deallocated automatically.
Conclusion
I hope you now have a better understanding of strong, weak, and unowned references. Xcode provides developers with built-in facilities to debug memory issues. For example, if you want to detect memory leaks, you can go up to the menu and select Product > Profile > Leaks. If you want us to write more about memory management and debugging tips, please leave us a comment and let us know.