Strong, weak, and unowned references — Swift 5 (iOS)
Different referencing types in Swift
Hello there!
I hope you are doing well! In this article, we shall take a look at how object references are declared in the Swift programming language and what are the different types of referencing techniques that can be used, and how.
Referencing in Swift
In Swift, only classes are declared as reference
type, whereas structs and enums are both value
types. So when classes are instantiated, ARC reserves a memory block to store its details and uses this address for reference.
According to Swift’s official documentation:
Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. This memory holds information about the type of the instance, together with the values of any stored properties associated with that instance.
On the other hand, no such references are allocated for structures
and enums
. They are passed as copies of values. According to Swift’s documentation:
Structures and enumerations are value types, not reference types, and aren’t stored and passed by reference.
Types of references
Swift supports three types of references —
- strong
- weak
- unowned
Strong reference —
Whenever we instantiate a class and assign it to a variable or a constant, a strong
reference is created. This reference keeps a stronghold of the instance and does not allow ARC to deallocate it. Thus, keeping it alive. This makes strong
very useful for development.
But even though, strong might seem useful and robust, it creates a problem of Strong Retain Cycles which are cyclic graphs like structures formed due to interdependency between references.
Let’s consider an example involving two classes — Car
and Owner
, where an owner may have a car and similarly a car may have an owner.
Driver Code -var owner: Owner? = Owner(name: "Ayush") // An owner
var car: Car? = Car(brand: "Tata") // A carowner?.car = car
car?.owner = owner
It is evident from the above example that Owner
holds a strong reference to a Car
instance, while Car
holds a strong reference to an Owner
instance, hence, creating an internal retain cycle. Let’s see what happens if we try to solve this by breaking the references and assigning nil
to the variables.
owner = nil // Break the reference
car = nil // Break the reference
What we see is that even when the references to Owner
and Car
instances are broken, the internal retain cycle is not removed and it restricts ARC from deallocating the instances. As a result, they continue to live. The problem here is that this type of memory retention might lead to memory leaks in the application. In order to solve this, Swift developers came up with— weak
and unowned
.
Weak reference —
Weak belongs to a different category of references that allows ARC to deallocate an instance even when active references(not strong) exist. So unlike strong, weak references do not restrict ARC from doing its job. Whenever ARC deallocates an instance, it automatically assigns a nil
value to all its weak
references. Now, since ARC can update the value of weak references at runtime, they must always be declared as a var
and optional
.
In the above example of Car
and Owner
, we saw that a retain cycle was created because of strong
references. So let's modify the Owner
class a bit.
Since the owner
may or may not have a car
, we will make car
an optional weak
variable.
// In class Owner, replace "var car: Car?" withweak var car: Car? // Making car weak
In the above example, if we now try to break the reference of car
by assigning it nil
, the owner
does not try to retain the instance because it has a weak
reference.
var car = nil
In this case, no retain cycle is created as car
is a weak
reference and hence its instance is deallocated, which is evident from the output on the console — the deinit
statement of class Car
.
Unowned reference —
Unowned is the third type of reference. Similar to weak
, it also allows ARC to deallocate an instance if it has no active strong
references to it. But unlike weak, an unowned variable cannot hold nil value which is why ARC does not assign it nil at runtime.
However, an unowned
reference should be used only when one is sure that the other instance will always outlive or has a longer lifetime. Since the unowned instance always outlives the containing instance, it is always expected to have a value. According to Swift’s documentation —
If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.
Let’s consider a different example of two entities — Car
and Engine
. An engine is always attached to a Car and cannot outlive it.
Driver Code -var car: Car? = Car(brand: "Tata") // A car
car.engine = Engine(type: "v8", car: car!)
Here, var car
holds a strong reference to a car, which holds an instance to an Engine
object. That contains an unowned car
reference, as engine
cannot outlive car
itself. Hence, no internal retain cycle is formed. If we now destroy the car
reference, the engine
will be automatically destroyed and memory will be reclaimed by ARC.
var car = nil
As soon as the above statement is executed, the deinit
is executed and prints
Car deallocated
Engine deallocated
Thus, we see what the uses and the objectives of the three types of references in Swift are and how they differ from each other.
Thank you for following along. I hope this article has been helpful.
Bye for now! 👋
Happy Coding 💻