When using an app, there is nothing worse than it being slow and unresponsive. Computer users have come to expect every piece of software to respond immediately to every interaction. Even the most feature-rich app will be ruined if it is unpleasant to use because it doesn't manage the device resources effectively. Also, with the growing popularity of mobile computers and devices, it is more important than ever to write software that uses battery power efficiently. One of the aspects of writing software that has the largest impact on both responsiveness and battery power is memory management.
Before we start looking at the code, we need to understand in some detail how data is represented in a computer. The common cliché is that all data in a computer is in 1s and 0s. This is true, but not so important when talking about memory management. Instead, we are concerned about where the data is stored. All computers, whether a desktop, laptop, tablet, or phone, store data in two places.
The first place we normally think of is the file system. It is stored on a dedicated piece of hardware; this is called a hard disk drive in many computers, but more recently, some computers have started to use solid-state drives. The other thing we hear about when buying computers is the amount of "memory" it has. Computer memory comes in "sticks" which hold less information than normal drives. All data, even if primarily stored on the Internet somewhere, must be loaded into the computer's memory so that we can interact with it.
Let's take a look at what that means for us as programmers.
The file system is designed for long-term storage of data. It is far slower to access than memory, but it is much more cost effective for storing a lot of data. As the name implies, the file system is simply a hierarchical tree of files, which we as users can interact with directly using the Finder on a Mac. This file system still exists on iPhones and iPads but it is hidden from us. However, software can still read and write the file system, thus allowing us to store data permanently, even after turning the device off.
Memory is a little more complex than the file system. It is designed to store the necessary data, temporarily for the software running currently. Unlike with a file system, all memory is lost as soon as you turn off your device. The analogy is similar to how we humans have short-term and long-term memory. While we are having a conversation or thinking about something, we have a certain subset of the information we are actively thinking about and the rest is in our long-term memory. In order to actively think about something, we have to recall it from our long-term memory into our short-term memory.
This is important to consider when programming because we want to reduce the amount of memory that we use at any given time. Using a lot of memory doesn't only negatively affect our own software; it can negatively affect the entire computer's performance. Also, when the operating system has to resort to using the file system, the extra processing and extra access to a second piece of hardware causes more power usage.
Now that we understand our goal, we can start discussing how we manage memory better in Swift.
All variables and constants in Swift are stored in memory. In fact, unless you explicitly write data to the file system, everything you create is going to be in memory. In Swift, there are two different categories of types. These two categories are value types and reference types. The only way in which they differ is in the way they behave when they get assigned to new variables, passed into methods, or captured in closures. Essentially, they only differ when you try to assign a new variable or constant to the value of an existing variable or constant.
A value type is any type that is defined as either a structure or an enumeration, while all classes are reference types. This is easy to determine for your own custom types based on how you declared them. Beyond that, all of the built-in types for Swift, such as strings, arrays, and dictionaries are value types. If you are ever uncertain, you can test any of the two types you want in a playground, to see if its behavior is consistent with a value type or a reference type. The simplest behavior to check is what happens on assignment.
When a value type is reassigned, it is copied so that afterwards each variable or constant holds a distinct value that can be changed independently. Let's take a look at a simple example using a string:
On the other hand, let's take a look at what happens with a reference type:
In the real world, this would be like two kids sharing a toy. Both can play with the toy but if one breaks the toy, it is broken for both kids.
This will be like buying a new toy for one of the kids.
This shows you that a reference type is actually a special version of a value type. The difference is that a reference type is not itself an instance of any type. It is simply a way to refer to another instance, sort of like a placeholder. You can copy the reference so that you have two variables referencing the same instance, or you can give a variable a completely new reference to a new instance. With reference types, there is an extra layer of indirection based on sharing instances between multiple variables.
Another place where the behavior of a value type differs from a reference type is when passing them into functions and methods. However, the behavior is very simple to remember if you look at passing a variable or constant into a function as just another assignment. This means that when you pass a value type into a function, it is copied while a reference type still shares the same instance:
func setNameOfPerson(person: Person, var to name: String) { person.name = name name = "Other Name" }
However, when we change the string within the function, the String
passed into it remains unchanged.
If we simply change the value of this variable, we will get the same behavior as if it were not an inout
parameter. However, we can also change where the inner reference is referring to by declaring it as an inout
parameter:
However, we then also assign insidePerson
to a completely new instance of the Person
reference type. This results in person
and person2
outside of the function pointing at two completely different instances of Person
leaving the name of person2
to be "New Name"
and updating the name of person
to "New Person"
:
The last behavior we have to worry about is when variables are captured within closures. This is what we did not cover about closures in Chapter 5, A Modern Paradigm – Closures and Functional Programming. Closures can actually use the variables that were defined in the same scope as the closure itself:
var nameToPrint = "Kai" var printName = { print(nameToPrint) } printName() // "Kai"
This is very different from normal parameters that we have seen before. We actually do not specify nameToPrint
as a parameter, nor do we pass it in when calling the method. Instead, the closure captures the nameToPrint
variable that is defined before it. These types of captures act similarly to inout
parameters in functions.
When a value type is captured, it can be changed and it will change the original value as well:
var outsideName = "Kai" var setName = { outsideName = "New Name" } print(outsideName) // "Kai" setName() print(outsideName) // "New Name"
As you can see, outsideName
was changed after the closure was called. This is exactly like an inout
parameter.
When a reference type is captured, any changes will also be applied to the outside version of the variable:
var outsidePerson = Person(name: "Kai") var setPersonName = { outsidePerson.name = "New Name" } print(outsidePerson.name) // "Kai" setPersonName() print(outsidePerson.name) // "New Name"
This is also exactly like an inout
parameter.
The other part of closure capture that we need to keep in mind is that changing the captured value after the closure is defined will still affect the value within the closure. We can take advantage of this to use the printName
closure we defined in the preceding section to print any name:
nameToPrint = "Kai" printName() // Kai nameToPrint = "New Name" printName() // "New Name"
Another way to avoid this behavior is to use a feature called capture lists. With this, you can specify the variables that you want to capture by copying them:
is any type that is defined as either a structure or an enumeration, while all classes are reference types. This is easy to determine for your own custom types based on how you declared them. Beyond that, all of the built-in types for Swift, such as strings, arrays, and dictionaries are value types. If you are ever uncertain, you can test any of the two types you want in a playground, to see if its behavior is consistent with a value type or a reference type. The simplest behavior to check is what happens on assignment.
When a value type is reassigned, it is copied so that afterwards each variable or constant holds a distinct value that can be changed independently. Let's take a look at a simple example using a string:
On the other hand, let's take a look at what happens with a reference type:
In the real world, this would be like two kids sharing a toy. Both can play with the toy but if one breaks the toy, it is broken for both kids.
This will be like buying a new toy for one of the kids.
This shows you that a reference type is actually a special version of a value type. The difference is that a reference type is not itself an instance of any type. It is simply a way to refer to another instance, sort of like a placeholder. You can copy the reference so that you have two variables referencing the same instance, or you can give a variable a completely new reference to a new instance. With reference types, there is an extra layer of indirection based on sharing instances between multiple variables.
Another place where the behavior of a value type differs from a reference type is when passing them into functions and methods. However, the behavior is very simple to remember if you look at passing a variable or constant into a function as just another assignment. This means that when you pass a value type into a function, it is copied while a reference type still shares the same instance:
func setNameOfPerson(person: Person, var to name: String) { person.name = name name = "Other Name" }
However, when we change the string within the function, the String
passed into it remains unchanged.
If we simply change the value of this variable, we will get the same behavior as if it were not an inout
parameter. However, we can also change where the inner reference is referring to by declaring it as an inout
parameter:
However, we then also assign insidePerson
to a completely new instance of the Person
reference type. This results in person
and person2
outside of the function pointing at two completely different instances of Person
leaving the name of person2
to be "New Name"
and updating the name of person
to "New Person"
:
The last behavior we have to worry about is when variables are captured within closures. This is what we did not cover about closures in Chapter 5, A Modern Paradigm – Closures and Functional Programming. Closures can actually use the variables that were defined in the same scope as the closure itself:
var nameToPrint = "Kai" var printName = { print(nameToPrint) } printName() // "Kai"
This is very different from normal parameters that we have seen before. We actually do not specify nameToPrint
as a parameter, nor do we pass it in when calling the method. Instead, the closure captures the nameToPrint
variable that is defined before it. These types of captures act similarly to inout
parameters in functions.
When a value type is captured, it can be changed and it will change the original value as well:
var outsideName = "Kai" var setName = { outsideName = "New Name" } print(outsideName) // "Kai" setName() print(outsideName) // "New Name"
As you can see, outsideName
was changed after the closure was called. This is exactly like an inout
parameter.
When a reference type is captured, any changes will also be applied to the outside version of the variable:
var outsidePerson = Person(name: "Kai") var setPersonName = { outsidePerson.name = "New Name" } print(outsidePerson.name) // "Kai" setPersonName() print(outsidePerson.name) // "New Name"
This is also exactly like an inout
parameter.
The other part of closure capture that we need to keep in mind is that changing the captured value after the closure is defined will still affect the value within the closure. We can take advantage of this to use the printName
closure we defined in the preceding section to print any name:
nameToPrint = "Kai" printName() // Kai nameToPrint = "New Name" printName() // "New Name"
Another way to avoid this behavior is to use a feature called capture lists. With this, you can specify the variables that you want to capture by copying them:
On the other hand, let's take a look at what happens with a reference type:
In the real world, this would be like two kids sharing a toy. Both can play with the toy but if one breaks the toy, it is broken for both kids.
This will be like buying a new toy for one of the kids.
This shows you that a reference type is actually a special version of a value type. The difference is that a reference type is not itself an instance of any type. It is simply a way to refer to another instance, sort of like a placeholder. You can copy the reference so that you have two variables referencing the same instance, or you can give a variable a completely new reference to a new instance. With reference types, there is an extra layer of indirection based on sharing instances between multiple variables.
Another place where the behavior of a value type differs from a reference type is when passing them into functions and methods. However, the behavior is very simple to remember if you look at passing a variable or constant into a function as just another assignment. This means that when you pass a value type into a function, it is copied while a reference type still shares the same instance:
func setNameOfPerson(person: Person, var to name: String) { person.name = name name = "Other Name" }
However, when we change the string within the function, the String
passed into it remains unchanged.
If we simply change the value of this variable, we will get the same behavior as if it were not an inout
parameter. However, we can also change where the inner reference is referring to by declaring it as an inout
parameter:
However, we then also assign insidePerson
to a completely new instance of the Person
reference type. This results in person
and person2
outside of the function pointing at two completely different instances of Person
leaving the name of person2
to be "New Name"
and updating the name of person
to "New Person"
:
The last behavior we have to worry about is when variables are captured within closures. This is what we did not cover about closures in Chapter 5, A Modern Paradigm – Closures and Functional Programming. Closures can actually use the variables that were defined in the same scope as the closure itself:
var nameToPrint = "Kai" var printName = { print(nameToPrint) } printName() // "Kai"
This is very different from normal parameters that we have seen before. We actually do not specify nameToPrint
as a parameter, nor do we pass it in when calling the method. Instead, the closure captures the nameToPrint
variable that is defined before it. These types of captures act similarly to inout
parameters in functions.
When a value type is captured, it can be changed and it will change the original value as well:
var outsideName = "Kai" var setName = { outsideName = "New Name" } print(outsideName) // "Kai" setName() print(outsideName) // "New Name"
As you can see, outsideName
was changed after the closure was called. This is exactly like an inout
parameter.
When a reference type is captured, any changes will also be applied to the outside version of the variable:
var outsidePerson = Person(name: "Kai") var setPersonName = { outsidePerson.name = "New Name" } print(outsidePerson.name) // "Kai" setPersonName() print(outsidePerson.name) // "New Name"
This is also exactly like an inout
parameter.
The other part of closure capture that we need to keep in mind is that changing the captured value after the closure is defined will still affect the value within the closure. We can take advantage of this to use the printName
closure we defined in the preceding section to print any name:
nameToPrint = "Kai" printName() // Kai nameToPrint = "New Name" printName() // "New Name"
Another way to avoid this behavior is to use a feature called capture lists. With this, you can specify the variables that you want to capture by copying them:
func setNameOfPerson(person: Person, var to name: String) { person.name = name name = "Other Name" }
However, when we change the string within the function, the String
passed into it remains unchanged.
If we simply change the value of this variable, we will get the same behavior as if it were not an inout
parameter. However, we can also change where the inner reference is referring to by declaring it as an inout
parameter:
However, we then also assign insidePerson
to a completely new instance of the Person
reference type. This results in person
and person2
outside of the function pointing at two completely different instances of Person
leaving the name of person2
to be "New Name"
and updating the name of person
to "New Person"
:
The last behavior we have to worry about is when variables are captured within closures. This is what we did not cover about closures in Chapter 5, A Modern Paradigm – Closures and Functional Programming. Closures can actually use the variables that were defined in the same scope as the closure itself:
var nameToPrint = "Kai" var printName = { print(nameToPrint) } printName() // "Kai"
This is very different from normal parameters that we have seen before. We actually do not specify nameToPrint
as a parameter, nor do we pass it in when calling the method. Instead, the closure captures the nameToPrint
variable that is defined before it. These types of captures act similarly to inout
parameters in functions.
When a value type is captured, it can be changed and it will change the original value as well:
var outsideName = "Kai" var setName = { outsideName = "New Name" } print(outsideName) // "Kai" setName() print(outsideName) // "New Name"
As you can see, outsideName
was changed after the closure was called. This is exactly like an inout
parameter.
When a reference type is captured, any changes will also be applied to the outside version of the variable:
var outsidePerson = Person(name: "Kai") var setPersonName = { outsidePerson.name = "New Name" } print(outsidePerson.name) // "Kai" setPersonName() print(outsidePerson.name) // "New Name"
This is also exactly like an inout
parameter.
The other part of closure capture that we need to keep in mind is that changing the captured value after the closure is defined will still affect the value within the closure. We can take advantage of this to use the printName
closure we defined in the preceding section to print any name:
nameToPrint = "Kai" printName() // Kai nameToPrint = "New Name" printName() // "New Name"
Another way to avoid this behavior is to use a feature called capture lists. With this, you can specify the variables that you want to capture by copying them:
last behavior we have to worry about is when variables are captured within closures. This is what we did not cover about closures in Chapter 5, A Modern Paradigm – Closures and Functional Programming. Closures can actually use the variables that were defined in the same scope as the closure itself:
var nameToPrint = "Kai" var printName = { print(nameToPrint) } printName() // "Kai"
This is very different from normal parameters that we have seen before. We actually do not specify nameToPrint
as a parameter, nor do we pass it in when calling the method. Instead, the closure captures the nameToPrint
variable that is defined before it. These types of captures act similarly to inout
parameters in functions.
When a value type is captured, it can be changed and it will change the original value as well:
var outsideName = "Kai" var setName = { outsideName = "New Name" } print(outsideName) // "Kai" setName() print(outsideName) // "New Name"
As you can see, outsideName
was changed after the closure was called. This is exactly like an inout
parameter.
When a reference type is captured, any changes will also be applied to the outside version of the variable:
var outsidePerson = Person(name: "Kai") var setPersonName = { outsidePerson.name = "New Name" } print(outsidePerson.name) // "Kai" setPersonName() print(outsidePerson.name) // "New Name"
This is also exactly like an inout
parameter.
The other part of closure capture that we need to keep in mind is that changing the captured value after the closure is defined will still affect the value within the closure. We can take advantage of this to use the printName
closure we defined in the preceding section to print any name:
nameToPrint = "Kai" printName() // Kai nameToPrint = "New Name" printName() // "New Name"
Another way to avoid this behavior is to use a feature called capture lists. With this, you can specify the variables that you want to capture by copying them:
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.
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) }
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:
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:
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.
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.
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.
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:
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:
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.
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.
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.
A great example of this type of relationship is with a car that must have a steering wheel:
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:
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.
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.
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.
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.
A strong reference cycle is when two instances directly or indirectly hold strong references to each other. This means that neither object can ever be deleted, because both are ensuring that the other will always exist.
A strong reference cycle between objects is when two types directly or indirectly contain strong references to each other.
A great example of a strong reference cycle between objects is if we rewrite our preceding car example without using a weak reference from SteeringWheel
to Car
:
Two objects can also indirectly hold strong references to each other through one or more third parties:
The best way as a developer to detect them is to use a tool built into Xcode called Instruments. Instruments can do many things, but one of those things is called Leaks. To run this tool you must have an Xcode Project; you cannot run it on a Playground. It is run by selecting Product | Profile from the menu bar.
This will build your project and display a series of profiling tools:
You can even select the Cycles & Roots view for the leaked objects and Instruments will show you a visual representation of your strong reference cycle. In the following screenshot, you can see that there is a cycle between SteeringWheel
and Car
:
Clearly, Leaks is a powerful tool and you should run it periodically on your code, but it will not catch all strong reference cycles. The last line of defense is going to be you staying vigilant with your code, always thinking about the ownership graph.
Of course, spotting cycles is only part of the battle. The other part of the battle is fixing them.
The easiest way to break a strong reference cycle is to simply remove one of the relationships completely. However, this is very often not going to be an option. A lot of the time, it is important to have a two-way relationship.
Now Car
has a strong reference to SteeringWheel
but there is only a weak reference back:
Unowned relationships are good for scenarios where the connection will never be missing. In our example, there are times that a SteeringWheel
exists without a car reference. If we change it so that the SteeringWheel
is created in the Car
initializer, we could make the reference unowned:
As we found out in Chapter 5, A Modern Paradigm – Closures and Functional Programming, closures are just another type of object, so they follow the same ARC rules. However, they are subtler than classes because of their ability to capture variables from their surrounding scope. These captures create strong references from the closures to the captured variable that are often overlooked because capturing variables looks so natural compared to conditionals, for loops and other similar syntax.
It is very common to provide closure properties that will be called whenever something occurs. These are generally called callbacks. Let's look at a ball class that has a callback for when the ball bounces:
This type of setup makes it easy to inadvertently create a strong reference cycle:
strong reference cycle between objects is when two types directly or indirectly contain strong references to each other.
A great example of a strong reference cycle between objects is if we rewrite our preceding car example without using a weak reference from SteeringWheel
to Car
:
Two objects can also indirectly hold strong references to each other through one or more third parties:
The best way as a developer to detect them is to use a tool built into Xcode called Instruments. Instruments can do many things, but one of those things is called Leaks. To run this tool you must have an Xcode Project; you cannot run it on a Playground. It is run by selecting Product | Profile from the menu bar.
This will build your project and display a series of profiling tools:
You can even select the Cycles & Roots view for the leaked objects and Instruments will show you a visual representation of your strong reference cycle. In the following screenshot, you can see that there is a cycle between SteeringWheel
and Car
:
Clearly, Leaks is a powerful tool and you should run it periodically on your code, but it will not catch all strong reference cycles. The last line of defense is going to be you staying vigilant with your code, always thinking about the ownership graph.
Of course, spotting cycles is only part of the battle. The other part of the battle is fixing them.
The easiest way to break a strong reference cycle is to simply remove one of the relationships completely. However, this is very often not going to be an option. A lot of the time, it is important to have a two-way relationship.
Now Car
has a strong reference to SteeringWheel
but there is only a weak reference back:
Unowned relationships are good for scenarios where the connection will never be missing. In our example, there are times that a SteeringWheel
exists without a car reference. If we change it so that the SteeringWheel
is created in the Car
initializer, we could make the reference unowned:
As we found out in Chapter 5, A Modern Paradigm – Closures and Functional Programming, closures are just another type of object, so they follow the same ARC rules. However, they are subtler than classes because of their ability to capture variables from their surrounding scope. These captures create strong references from the closures to the captured variable that are often overlooked because capturing variables looks so natural compared to conditionals, for loops and other similar syntax.
It is very common to provide closure properties that will be called whenever something occurs. These are generally called callbacks. Let's look at a ball class that has a callback for when the ball bounces:
This type of setup makes it easy to inadvertently create a strong reference cycle:
Two objects can also indirectly hold strong references to each other through one or more third parties:
The best way as a developer to detect them is to use a tool built into Xcode called Instruments. Instruments can do many things, but one of those things is called Leaks. To run this tool you must have an Xcode Project; you cannot run it on a Playground. It is run by selecting Product | Profile from the menu bar.
This will build your project and display a series of profiling tools:
You can even select the Cycles & Roots view for the leaked objects and Instruments will show you a visual representation of your strong reference cycle. In the following screenshot, you can see that there is a cycle between SteeringWheel
and Car
:
Clearly, Leaks is a powerful tool and you should run it periodically on your code, but it will not catch all strong reference cycles. The last line of defense is going to be you staying vigilant with your code, always thinking about the ownership graph.
Of course, spotting cycles is only part of the battle. The other part of the battle is fixing them.
The easiest way to break a strong reference cycle is to simply remove one of the relationships completely. However, this is very often not going to be an option. A lot of the time, it is important to have a two-way relationship.
Now Car
has a strong reference to SteeringWheel
but there is only a weak reference back:
Unowned relationships are good for scenarios where the connection will never be missing. In our example, there are times that a SteeringWheel
exists without a car reference. If we change it so that the SteeringWheel
is created in the Car
initializer, we could make the reference unowned:
As we found out in Chapter 5, A Modern Paradigm – Closures and Functional Programming, closures are just another type of object, so they follow the same ARC rules. However, they are subtler than classes because of their ability to capture variables from their surrounding scope. These captures create strong references from the closures to the captured variable that are often overlooked because capturing variables looks so natural compared to conditionals, for loops and other similar syntax.
It is very common to provide closure properties that will be called whenever something occurs. These are generally called callbacks. Let's look at a ball class that has a callback for when the ball bounces:
This type of setup makes it easy to inadvertently create a strong reference cycle:
Now Car
has a strong reference to SteeringWheel
but there is only a weak reference back:
Unowned relationships are good for scenarios where the connection will never be missing. In our example, there are times that a SteeringWheel
exists without a car reference. If we change it so that the SteeringWheel
is created in the Car
initializer, we could make the reference unowned:
As we found out in Chapter 5, A Modern Paradigm – Closures and Functional Programming, closures are just another type of object, so they follow the same ARC rules. However, they are subtler than classes because of their ability to capture variables from their surrounding scope. These captures create strong references from the closures to the captured variable that are often overlooked because capturing variables looks so natural compared to conditionals, for loops and other similar syntax.
It is very common to provide closure properties that will be called whenever something occurs. These are generally called callbacks. Let's look at a ball class that has a callback for when the ball bounces:
This type of setup makes it easy to inadvertently create a strong reference cycle:
found out in Chapter 5, A Modern Paradigm – Closures and Functional Programming, closures are just another type of object, so they follow the same ARC rules. However, they are subtler than classes because of their ability to capture variables from their surrounding scope. These captures create strong references from the closures to the captured variable that are often overlooked because capturing variables looks so natural compared to conditionals, for loops and other similar syntax.
It is very common to provide closure properties that will be called whenever something occurs. These are generally called callbacks. Let's look at a ball class that has a callback for when the ball bounces:
This type of setup makes it easy to inadvertently create a strong reference cycle:
It is a great idea to always keep strong reference cycles in mind, but if we are too aggressive with the use of weak and unowned references, we can run into the opposite problem, where an object is deleted before we intended it to be.
With an object this will happen if all of the references to the object are weak or unowned. This won't be a fatal mistake if we use weak references, but if this happens with an unowned reference it will crash your program.
For example, let's look at the preceding example with an extra weak reference:
Now that we have a good understanding of memory management, we are ready to discuss the full trade-offs we make when we choose to design a type as a structure or a class. With our ability to extend protocols like we saw in the previous chapter, we can achieve very similar functionality to the inheritance we saw with classes in Chapter 3, One Piece at a Time – Types, Scopes, and Projects. This means that we are often choosing between using a structure or a class based on the memory implications, or in other words, whether we want our type to be a value type or a reference type.
Value types have an advantage because they are very simple to reason about. You don't have to worry about multiple variables referencing the same instance. Even better, you don't have to worry about all of the potential problems we have discussed with strong reference cycles. However, there is still an advantage to reference types.
Reference types are advantageous when it really makes sense to share an instance between multiple variables. This is especially true when you are representing some sort of physical resource that makes no sense to copy like a port on the computer or the main window of an application. Also, some will argue that reference types use memory more efficiently, because it doesn't take up more memory with lots of copies floating around. However, the Swift compiler will actually do a lot of optimizing of our code and reduce or eliminate most of the copying that actually occurs when possible. For example, if we pass a value type into a function that never modifies the value, there is no reason to actually create that copy. Ultimately, I don't recommend optimizing for something like that before it becomes necessary. Sometimes you will run into memory problems with your application and then it can be appropriate to convert large types to classes if they are being copied a lot.
Ultimately, I recommend using structures and protocols as a default, because they greatly reduce complexity and fall back to classes only when it is required. I even recommend using protocols instead of super classes when possible, because they are easier to shift around and make it an easier transition between value types and reference types.