Swift · · 8 min read

Memory Management in Swift: Understanding Strong, Weak and Unowned References

Memory Management in Swift: Understanding Strong, Weak and Unowned References

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?

Editor’s note: If you are new to Swift, you can check out our free guide to get started.

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?

  1. The task will stop performing meaning you won’t be able to execute any of your task.
  2. The task probably will not progress but continue running and running until it hits the limit and the program crashed.
  3. 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.

arc-strong-weak-unowned

Strong vs Weak vs Unowned – Quick Facts

  1. Usually, when a property is being created, the reference is strong unless they are declared weak or unowned.
  2. With the property labelled as `weak`, it will not increment the reference count
  3. 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:

Memory Management in Swift

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.

memory-management-swift-sample-2

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.

memory-management-swift-sample-3

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:

memory-management-swift-unowned

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.

Read next