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