Automatic reference counting

Now that we understand the different ways in which data is represented in Swift, we can look into how we can manage the memory better. Every instance that we create takes up memory. Naturally, it wouldn't make sense to keep all data around forever. Swift needs to be able to free up memory so that it can be used for other purposes, once our program doesn't need it anymore. This is the key to managing memory in our apps. We need to make sure that Swift can free up all the memory that we no longer need, as soon as possible.

The way that Swift knows it can free up memory is when the code can no longer access an instance. If there is no longer any variable or constant referencing an instance, it can be repurposed for another instance. This is called "freeing the memory" or "deleting the object".

In Chapter 3, One Piece at a Time – Types, Scopes, and Projects we already discussed when a variable is accessible or not in the section about scopes. This makes memory management very simple for value types. Since value types are always copied when they are reassigned or passed into functions, they can be immediately deleted once they go out of scope. We can look at a simple example to get the full picture:

func printSomething() {
    let something = "Hello World!"
    print(something)
}

Here we have a very simple function that prints out "Hello World!". When printSomething is called, something is assigned to a new instance of String with the value "Hello World!". After print is called, the function exits and therefore something is no longer in scope. At that point, the memory being taken up by something can be freed.

While this is very simple, reference types are much more complex. At a high level, an instance of a reference type is deleted at the point that there is no longer any reference to the instance in scope anymore. This is relatively straightforward to understand but it gets more complex in the details. The Swift feature that manages this is called Automatic Reference Counting or ARC for short.

Object relationships

The key to ARC is that every object has relationships with one or more variables. This can be extended to include the idea that all objects have a relationship with other objects. For example, a car object would contain objects for its four tires, engine, and so on. It will also have a relationship with its manufacturer, dealership, and owner. ARC uses these relationships to determine when an object can be deleted. In Swift, there are three different types of relationships: strong, weak, and unowned.

Strong

The first, and default type of relationship is a strong relationship. It says that a variable requires the instance it is referring to always exist, as long as the variable is still in scope. This is the only behavior available for value types. When an instance no longer has any strong relationships to it, it will be deleted.

A great example of this type of relationship is with a car that must have a steering wheel:

class SteeringWheel {}

class Car {
    var steeringWheel: SteeringWheel
    
    init(steeringWheel: SteeringWheel) {
        self.steeringWheel = steeringWheel
    }
}

By default, the steeringWheel property has a strong relationship to the SteeringWheel instance it is initialized with. Conceptually, this means that the car itself has a strong relationship to the steering wheel. As long as a car exists, it must have a relationship to a steering wheel that exists. Since steeringWheel is declared as a variable, we could change the steering wheel of the car, which would remove the old strong relationship and add a new one, but a strong relationship will always exist.

If we were to create a new instance of Car and store it in a variable, that variable would have a strong relationship to the car:

let wheel = SteeringWheel()
let car = Car(steeringWheel: wheel)

Lets break down all the relationships in this code. First we create the wheel constant and assign it to a new instance of SteeringWheel. This sets up a strong relationship from wheel to the new instance. We do the same thing with the car constant, but this time we also pass in the wheel constant to the initializer. Now, not only does car have a strong relationship to the new Car instance, but the Car initializer also creates a strong relationship from the steeringWheel property to the same instance as the wheel constant:

Strong

So what does this relationship graph mean for memory management? At this time, the Car instance has one strong relationship: the car constant, and the SteeringWheel instance has two strong relationships: the wheel constant and the steeringWheel property of the Car instance.

This means that the Car instance will be deleted as soon as the car constant goes out of scope. On the other hand, the SteeringWheel instance will only be deleted after both the wheel constant goes out of scope and the Car instance is deleted.

You can envision a strong reference counter on every instance in your program. Every time a strong relationship is setup to an instance the counter goes up. Every time an object strongly referencing it gets deleted, the counter goes down. If that counter ever goes back to zero, the instance is deleted.

The other important thing to realize is that all relationships are only in one direction. Just because the Car instance has a strong relationship to the SteeringWheel instance does not mean that the SteeringWheel instance has any relationship back. You could add your own relationship back by adding a car property to the SteeringWheel class, but you have to be careful when doing this, as we will see in the strong reference cycle section coming up.

Weak

The next type of relationship in Swift is a weak relationship. It allows one object to reference another without enforcing that it always exists. A weak relationship does not contribute to the reference counter of an instance, which means that the addition of a weak relationship does not increase the counter nor does it decrease the counter when removed.

Since a weak relationship cannot guarantee that it will always exist, it must always be defined as an optional. A weak relationship is defined using the weak keyword before the variable declaration:

class SteeringWheel {
    weak var car: Car?
}

This allows a SteeringWheel to have a car assigned to it, without enforcing that the car never be deleted. The car initializer can then assign this backwards reference to itself:

class Car {
    var steeringWheel: SteeringWheel
    
    init(steeringWheel: SteeringWheel) {
        self.steeringWheel = steeringWheel
        self.steeringWheel.car = self
    }
}

If the car is ever deleted, the car property of SteeringWheel will automatically be set to nil. This allows us to gracefully handle the scenario that a weak relationship refers to an instance that has been deleted.

Unowned

The final type of relationship is an unowned relationship. This relationship is almost identical to a weak relationship. It also allows one object to reference another without contributing to the strong reference count. The only difference is that an unowned relationship does not need to be declared as optional and it uses the unowned keyword instead of weak. It acts similar to an implicitly unwrapped optional. You can interact with an unowned relationship as if it were a strong relationship, but if the unowned instance has been deleted and you try to access it, your entire program will crash. This means that you should only use unowned relationships in scenarios where the unowned object will never actually be deleted while the primary object still exists.

You may ask then, "Why would we not always use a strong relationship instead?" The answer is that sometimes unowned or weak references are needed to break something called a strong reference cycle.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset