When to use strong, weak and unowned reference types in Swift and why

Posted by Oleksandr Kruk

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:

class Balance {
  let amount: NSDecimalNumber
  let currency: String
}

class Lender {
  let name: String
  var availableFunds: Balance

  init(name: String, availableFunds: Balance) {
    self.name = name
    self.availableFunds = availableFunds
  }
}

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.

...
self.availableFunds = availableFunds // reference count += 1
...

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:

var balance = Balance(100.0, "GBP") // ref count 1
var balanceCopy: Balance? = balance // ref count 2
let balanceConstant = balance // ref count 3
balanceCopy = nil // ref count 2

strong reference diagram

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:


var balance: Balance? = Balance(200.0, "GBP") // ref count 1
weak var balanceCopy = balance // ref count 1

balance = nil // ref count 0
balanceCopy?.amount // nil

weak reference diagram

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:

var balance: Balance? = Balance(100.0, "GBP") // ref count 1
unowned var balanceCopy = balance! // ref count 1
balance = nil // Balance object ref count 1
balanceCopy.amount // runtime error

unowned reference diagram

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.

protocol LenderDepositDelegate: AnyObject {
  func deposit(amount: DecimalNumber)
}

class AvailableFundsViewController: UIViewController,
                                    LenderDepositDelegate {
  func deposit(amount: DecimalNumber) {
    lender.availableFunds = lender.availableFunds.adding(amount)
  }
}

class AvailableFundsView: UIView {
    var delegate: LenderDepositDelegate

    @IBAction func depositTenPounds(_ sender: Any) {
      delegate.deposit(10)
    }
}

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. reference cycle 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.

class AvailableFundsView: UIView {
    weak var delegate: LenderDepositDelegate?

    @IBAction func depositTenPounds(_ sender: Any) {
      delegate?.deposit(10)
    }
}

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. broken ref cycle 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.

class Balance {
  let amount: NSDecimalNumber
  unowned let lender: Lender

  init(_ amount: NSDecimalNumber, lender: Lender) {
    self.amount = amount
    self.lender = lender
  }
}

class Lender {
    let name: String
    var balance: Balance?

    init(aName: String) {
      name = aName
    }
}

var lender = Lender(name: Matt)
lender.balance = Balance(20.0, aLender: lender)

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


  1. 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