When to use strong, weak and unowned reference types in Swift and why
We have been using Swift at Funding Circle for a couple of years now. One particular subject that was interesting to get to the bottom of was the usage of weak
vs unowned
reference types. Why are there three ways (strong
, weak
, unowned
) of referencing an object and when should we use each.
First lets understand why is the reference counting important. Swift does its memory management by relying on the ARC. As name implies, it counts references in order to understand if an object has to be kept in memory or not. That’s an extremely simplified explanation, to learn more please refer to Apple’s ARC documentation1.
Before we start lets make sure our communication is clear. When talking about variables, we are referring to the Swift reference types (class
and function
). Reference counting does not work the same way for value types (struct
, enum
and basic types). Swift does a copy on write whenever a value type is being changed. This means that, in general, we can assume that in practice a value type has a reference count of at most 1. In reality the values also point to one same instance up to the point when one of the values is being changed, only at that point a copy of the original value will be done, then changed and persisted in its own allocated memory.
Strong references
This is the default reference type in Swift. Whenever we declare a variable without specifying its reference type, it will always be strong
. A reference being strong
means that the ARC will increment the reference count for the object being referenced by a variable. This impacts memory management since an object’s memory can not be released while the reference count is greater than zero.
Lets look at the following simplified example:
Although in real world Balance entity would be a great candidate for being a struct
, we made it a class
for demonstration purposes. So we have two entities, Balance and Lender. Lender’s property availableFunds
holds a strong
reference to an object of type Balance
. At the point when we assign the Balance
object to a named variable, the object’s reference count is incremented by one. This means that there is one more variable pointing to the memory that was allocated for that object. The implication of this assignment is that the Balance object can not be deallocated while the Lender instance exists.
An important note about this example is that in Swift the arguments are constants by default and can not be changed. This means that when we assign the argument to the property, a copy of the argument will be made when it’s being changed.
Multiple variables pointing to the same object increment it’s reference counting as we can see in this snippet:
A constant will have always a strong reference type, so when we declare let balanceConstant
it will increment the reference counting of the Balance
object in memory. Whenever a variable that points to an object which is as well referenced by a constant is changed, a copy of the object will be made and that copy is the one being updated.
Weak reference
Contrary to strong
reference, weak
reference has no impact on an object’s reference count. Meaning that if we declare a weak
variable pointing to an object, that object’s reference count will remain the same as it was before. Lets see what that means in practice with the following simple example:
We start by creating a balance
variable that will hold a strong
reference to the newly created Balance
object. This increments the object’s reference count and makes it equal to one. Next we declare a weak
variable balanceCopy
which will not change the object’s reference count. We then remove the strong
reference from the object by assigning nil
to the balance
variable that was holding strong
reference to the Balance
object. This brings the reference count to zero and consequently deallocates the object which means that our weak balanceCopy
variable will have no object to point to and thus when we try to unwrap it, the result is nil
.
Unowned reference
Similarly to weak
reference, an unowned
reference does not increment the object’s reference count. But there are several important differences in its usage. One of the differences between weak
and unowned
is the fact that Swift runtime keeps a secondary reference count for unowned
references. When the strong
references count goes to zero, the object will release all the references it has but the object’s own memory won’t be released while there are unowned
references pointing to it. The object’s memory is marked as zombie
though. It means that the user can not rely on whatever is stored in that memory and accessing it, without a safe unwrap, will crash the program. The check of the reference happens at runtime, that’s why accessing it is a runtime error. Another difference is that unowned
variables can not be of optional type. This is very important given that Swift will force us to use the variable without being able to double check if it’s pointing to a valid object. Lets look at the following example:
To start with, we declare an optional balance
variable that holds a strong
reference to Balance
object. In the next line we declare an unowned
variable balanceCopy
, which will point to the same Balance
object as the balance
variable but will not change the object’s reference count. When we then assign nil
to the balance
variable, the Balance
object is marked as zombie, so its memory is not accessible and thus when we try to get the amount on balanceCopy we get a runtime error.
When to use a strong reference
This one is simple, given the examples that we have seen. It becomes clear that we should use a strong
reference whenever we want to guarantee that we are always able to access the variable. This is specially true for things like object properties which should always exist during their owner’s lifetime. To reiterate, in our case, when we have a Lender object we know he will definitely have a name and will have a balance, even if he has just created his account with £0.00. For more detailed examples check Apple’s examples1.
So when strong reference is not advised
The situation becomes more complicated when we start having bidirectional references between objects. Such references are common in delegate pattern as we can see in the following example that uses the LenderDepositDelegate
to define a deposit
operation.
Usually in this case the view receives some input event from the user and delegates the handling of that event to the controller (which is also the delegate in our case), and for doing that the view has a reference to the controller. The problem is that the controller also has a reference to the view (otherwise the view would be immediately deallocated after being initialized because its reference count would be zero). This creates a reference cycle as you can see in the diagram below.
It clearly depicts a situation where, when strong
reference is used, neither of the objects can be deallocated because they always have at least one other object pointing to them.
So it’s good practice in delegate pattern to never strongly reference the delegate, as we can see in the snippet below.
By doing so, we break the reference cycle and allow for the AvailableFundsViewController
to be deallocated when it’s no longer in use (e.g.: we navigate to a different controller). And that in turn leaves the AvailableFundsView
with reference count zero and consequently makes it possible for the view to be deallocated as depicted in the following diagram.
This is a very simple case though, in the real world things are much more complex to analyse, but this should be enough to understand the general idea behind it.
When to prefer weak over unowned
As we have seen, there is no practical difference between weak
and unowned
in terms of the reference count of an object. The difference between the two lies in the mechanism that the Swift language uses to handle them. When we use the weak
reference, Swift explicitly makes the variable an optional and doesn’t let us use it unless we unwrap it. This creates a mandatory compile time verification of the reference, meaning that we will not be allowed to use it without checking it first. This obviously can be defeated by using force unwrap !
, which is generally discouraged unless we have some kind of guarantee that the variable is pointing to a valid object. In case of unowned
reference, it is implicitly unwrapped, meaning that the reference will only be verified at runtime, which leads to a runtime crash if the reference points to deallocated object. This mechanism makes the weak
reference type the safe choice if we don’t want to have surprises when our program is running.
So when should we use unowned
? The rule here is to use it iff we can guarantee that the lifecycle of the referenced object is equal or greater than the lifetime of the variable pointing to it. In that case we know for sure that the object will not be deallocated and we can safely use it. Let’s adapt our previous example in order to demonstrate a possible usage of unowned
property.
In the example above it’s safe to declare balance owner Lender as unowned
because we know that Lender will always exist while Balance exists. In case lender is deallocated, the Balance gets deallocated as well. This means it’s safe to use unowned
since the Lender will always exist during the lifetime of Balance object.
Although the unowned
reference has a slight performance benefit over weak
reference, it’s not significant for most of the projects out there. In real scenarios it is very hard to guarantee the pre-condition of the referenced object being always available during the lifecycle of the variable that is pointing to it, unless the relation between the entities is such that it guarantees this condition1. And even when it’s theoretically easy to prove such conditions, nothing prevents somebody from changing the code in a way that that condition does not hold anymore and we unintentionally have a runtime crash.
Recap
We have seen that strong
, weak
and unowned
each have their own clear use cases. The strong
reference is useful for declaring the attributes of an entity. The weak
reference is a safe way to break reference cycles. As for unowned
, it has a very specific use case, for when we know that the pointee’s lifecycle is at least as long as the pointer’s lifecycle. In real world though, we are always at risk of our assumptions being changed and no longer hold, leading our once safe code to a runtime crash. For this reason, weak
reference is always safer to use than unowned
.
Resources
- Apple provides a guide with examples of when each reference type makes sense and how to break reference cycles1
- Another useful reference is the Advanced Swift 4 book by Chris Eidhof, Ole Begemann and AirspeedVelocity
Apple offers a good description about how the ARC works and they provide example scenarios for when to use each type of reference types. You can read about it here.
Originally published at engineering.fundingcircle.com on April 27, 2018.