As a modern, high-level programming language, Swift handles much of the memory management of your apps and allocates or deallocates memory on your behalf. It does so using a neat technology called Automatic Reference Counting, or ARC. In this tutorial, you’ll learn all about ARC and memory management in Swift.
With an understanding of this system, you can influence when the life of a heap object ends. Swift uses ARC to be extremely predictable and efficient in resource constrained environments — like iOS.
Because ARC is “automatic”, you don’t have explicitly participate in the reference counting of objects. You do, however, need to consider relationships between objects to avoid memory leaks. This is a very important requirement often overlooked by new developers.
In this tutorial, you’ll level up your Swift and ARC skills by learning the following:
- How ARC works.
- What reference cycles are and how to break them.
- An example of a reference cycle in practice, and how to detect one with the latest Xcode visualization tools.
- How to deal with mixed value and reference types.
Getting Started
Open Xcode and click File\New\Playground…. Select the iOS Platform, name it MemoryManagement and then click Next. Save it wherever you wish and then remove the boilerplate code and save the playground.
Next, add the following code to your playground:
class User { var name: String init(name: String) { self.name = name print("User \(name) is initialized") } deinit { print("User \(name) is being deallocated") } } let user1 = User(name: "John") |
This defines a class called User
and creates an instance of it. The class User
has one property, name
, an init
method (called just after memory allocation) and a deinit
method (called just prior to deallocation). The print
statements let you see what is happening.
You will notice that the playground shows "User John is initialized\n"
in the sidebar, on the print
statement within init
. However, you will notice that the print
statement within deinit
is never called. This means that the object is never deinitialized, which means it’s never deallocated. That’s because the scope in which it was initialized is never closed — the playground itself never goes out of scope — so the object is not removed from memory.
Change the let user1
initialization by wrapping it in a do
like so:
do { let user1 = User(name: "John") } |
This creates a scope around the initialization of the user1
object. At the end of the scope, we would expect user1
to be deallocated.
Now you see both print
statements that correspond to initialization and deinitialization in the sidebar. This shows the object is being deinitialized at the end of the scope, just before it’s removed from memory.
The lifetime of a Swift object consists of five stages:
- Allocation (memory taken from stack or heap)
- Initialization (
init
code runs) - Usage (the object is used)
- Deinitialization (
deinit
code runs) - Deallocation (memory returned to stack or heap)
While there are no direct hooks into allocation and deallocation, you can use print
statements in init
and deinit
as a proxy for monitoring those processes. Sometimes “deallocate” and “deinit” are used interchangeably, but they are actually two distinct stages in an object’s lifetime.
Reference counting is the mechanism by which an object is deallocated when it is no longer required. The question here is “when can you be sure an object won’t be needed in the future?” Reference counting manages this by keeping a usage count, also known as the reference count, inside each object instance.
This count indicates how many “things” reference the object. When the reference count of an objects hits zero no clients of the object remain, so the object deinitializes and deallocates.
When you initialize the User
object, it starts with a reference count of 1 since the constant user1
references that object. At the end of the do
block, user1
goes out of scope, the count decrements, and the reference count decrements to zero. As a result, user1
deinitializes and subsequently deallocates.
Reference cycles
In most cases, ARC works like a charm; as a developer, you don’t usually have to worry about memory leaks, where unused objects stay alive indefinitely.
But it’s not all plain sailing! Leaks can happen!
How can leaks happen? Imagine a situation where two objects are no longer required, but each reference one another. Since each has a non-zero reference count, deallocation, of both objects, can never occur.
This is called a strong reference cycle. It fools ARC and prevents it from cleaning up. As you can see, the reference count at the end is not zero, and thus object1
and object2
are never deallocated even though they’re no longer required.
To see this in action, add the following code after the User
class but before the existing do
block:
class Phone { let model: String var owner: User? init(model: String) { self.model = model print("Phone \(model) is initialized") } deinit { print("Phone \(model) is being deallocated") } } |
Then change the do
block to look like this:
do { let user1 = User(name: "John") let iPhone = Phone(model: "iPhone 6s Plus") } |
This adds a new class called Phone
, and creates an instance of this new class.
This new class is rather simple: two properties, one for the model and one for the owner, an init
and a deinit
method. The owner
property is optional, since a Phone
can exist without a User
.
Next, add the following code to the User
class, immediately after the name
property:
private(set) var phones: [Phone] = [] func add(phone: Phone) { phones.append(phone) phone.owner = self } |
This adds a phones
array property to hold all phones owned by a user. The setter is made private so that clients are forced to use add(phone:)
. This method ensures that owner
is set properly when you add it.
Currently, as you can see in the sidebar, both Phone
and User
objects deallocate as expected.
But now make the do
block look like this:
do { let user1 = User(name: "John") let iPhone = Phone(model: "iPhone 6s Plus") user1.add(phone: iPhone) } |
Here, you add iPhone
to user1
. This automatically sets the owner
property of iPhone
to user1
. A strong reference cycle between the two objects prevents ARC from deallocating them. As a result, both user1
and iPhone
never deallocate.
Weak References
To break strong reference cycles, you can specify the relationship between reference counted objects as weak
. Unless otherwise specified, all references are strong. Weak references, by contrast, don’t increase the strong reference count of the object.
In other words, weak references don’t participate in the lifecycle management of the object. Additionally, weak references are always declared as optional types. This means when the reference count goes to zero, the reference can automatically be set to nil
.
In the image above, the dashed arrow represents a weak reference. Notice how the reference count of object1
is 1 because variable1
refers to it. The reference count of object2
is 2, because both variable2
and object1
refer to it. While object2
references object1
, it does so weakly, meaning it doesn’t affect the strong reference count of object1
.
When both variable1
and variable2
go away, object1
will be left with a reference count of zero and deinit
will be called. This removes the strong reference to object2
which then subsequently deinitializes.
Back in your playground, break the User
–Phone
reference cycle by making the owner
reference weak as shown below:
class Phone { weak var owner: User? // other code... } |
Now user1
and iPhone
deallocate properly at the end of the do
block as you can see in the results sidebar.
Unowned References
There is another reference modifier you can use that doesn’t increase the reference count: unowned
.
What’s the difference between unowned
and weak
? A weak
reference is always optional and automatically becomes nil
when the referenced object deinitializes. That’s why you must define weak properties as optional var
types for your code to compile (because the variable needs to change).
Unowned references, by contrast, are never optional types. If you try to access an unowned property that refers to a deinitialized object, you will trigger a runtime error comparable to force unwrapping a nil
optional type.
Time to get some practice using unowned
. Add a new class CarrierSubscription
before the do
block as shown:
class CarrierSubscription { let name: String let countryCode: String let number: String let user: User init(name: String, countryCode: String, number: String, user: User) { self.name = name self.countryCode = countryCode self.number = number self.user = user print("CarrierSubscription \(name) is initialized") } deinit { print("CarrierSubscription \(name) is being deallocated") } } |
CarrierSubscription
has four properties: the name
of the subscription, the countryCode
and phone number
, and a reference to a User
object.
Next, add the following to the User
class, after the name
property:
var subscriptions: [CarrierSubscription] = [] |
This adds a subscriptions
property, which will hold an array of CarrierSubscrition
objects.
Also, add the following to the top of the Phone
class, after the owner
property:
var carrierSubscription: CarrierSubscription? func provision(carrierSubscription: CarrierSubscription) { self.carrierSubscription = carrierSubscription } func decommission() { self.carrierSubscription = nil } |
This adds an optional CarrierSubscription
property and two new functions to provision
and decommission
a carrier subscription on the phone.
Next, add the following to init
inside CarrierSubscription
, just before the print
statement:
user.subscriptions.append(self) |
This ensures that this CarrierSubscription
is added to the user’s array of subscriptions.
Finally, change the do
block to look like this:
do { let user1 = User(name: "John") let iPhone = Phone(model: "iPhone 6s Plus") user1.add(phone: iPhone) let subscription1 = CarrierSubscription(name: "TelBel", countryCode: "0032", number: "31415926", user: user1) iPhone.provision(carrierSubscription: subscription1) } |
Notice what is printed in the results sidebar. Again you see a reference cycle: neither user1
, iPhone
or subscription1
are deallocated at the end. Can you find where the issue is now?
Either the reference from user1
to subscription1
or the reference from subscription1
to user1
should be unowned
to break the cycle. The question is which of the two to choose. This is where a little bit of knowledge of your domain helps.
A user owns a carrier subscription, and (contrary to what carriers may think!), the carrier subscription does not own the user. Moreover, it doesn’t make sense for a CarrierSubscription
to exist without an owning User
. This is why you declared it as an immutable let
property in the first place.
Since a User
with no CarrierSubscription
can exist, but no CarrierSubscription
can exist without a User
, the user
reference should be unowned.
Add unowned
to the user
property of CarrierSubscription
like so:
class CarrierSubscription { let name: String let countryCode: String let number: String unowned let user: User // Other code... } |
This breaks the reference cycle and lets every object deallocate.
Reference Cycles with Closures
Reference cycles for objects occur when properties reference each other. Like objects, closures are also reference types and can cause cycles. Closures capture (or close over) the objects that they operate on.
For example, if a closure is assigned to the property of a class, and that closure uses instance properties of that same class, you have a reference cycle. In other words, the object holds a reference to the closure via a stored property; the closure holds a reference to the object via the captured value of self
.
Add the following to CarrierSubscription
, just after the user
property:
lazy var completePhoneNumber: () -> String = { self.countryCode + " " + self.number } |
This closure computes and returns a complete phone number. The property is declared with lazy
, meaning that it will not be assigned until it’s used the first time. This is required because it’s using self.countryCode
and self.number
, which aren’t available until after the initializer runs.
Add the following line at the end of the do
block:
print(subscription1.completePhoneNumber()) |
You will notice that user1
and iPhone
deallocate, but not CarrierSubscription
due to the strong reference cycle between the object and the closure.
Swift has a simple, elegant way to break strong reference cycles in closures. You declare a capture list in which you define the relationships between the closure and the objects it captures.
To illustrate how the capture list works, consider the following code:
var x = 5 var y = 5 let someClosure = { [x] in print("\(x), \(y)") } x = 6 y = 6 someClosure() // Prints 5, 6 print("\(x), \(y)") // Prints 6, 6 |
The variable x
is in the capture list, so a copy of x
is made at the point the closure is defined. It is said to be captured by value. On the other hand, y
is not in the capture list, and is instead captured by reference. This means that when the closure runs, y
will be whatever it is at that point, rather than at the point of capture.
Capture lists come in handy for defining a weak
or unowned
relationship between objects used in a closure. In this case, unowned
is a good fit since the closure could not exist while the instance of CarrierSubscription
has gone away.
Change the completePhoneNumber
closure in CarrierSubscription
to look like this:
lazy var completePhoneNumber: () -> String = { [unowned self] in return self.countryCode + " " + self.number } |
This adds [unowned self]
to the capture list for the closure. It means that self
is captured as an unowned reference instead of a strong reference.
This solves the reference cycle. Hooray!
The syntax used here is actually a shorthand for a longer capture syntax, which introduces a new identifier. Consider the longer form:
var closure = { [unowned newID = self] in // Use unowned newID here... } |
Here, newID
is an unowned
copy of self
. Outside the closure’s scope, self
keeps its original meaning. In the short form, which you used above, a new self
variable is created which shadows the existing self
variable just for the closure’s scope.
In your code, the relationship between self
and the completePhoneNumber
closure is unowned
. If you are sure that a referenced object from a closure will never deallocate, you can use unowned
. If it does deallocate, you are in trouble.
Add the following code to the end of the playground:
// A class that generates WWDC Hello greetings. See http://wwdcwall.com class WWDCGreeting { let who: String init(who: String) { self.who = who } lazy var greetingMaker: () -> String = { [unowned self] in return "Hello \(self.who)." } } let greetingMaker: () -> String do { let mermaid = WWDCGreeting(who: "caffinated mermaid") greetingMaker = mermaid.greetingMaker } greetingMaker() // TRAP! |
The playground hits a runtime exception because the closure expects self.who
to still be valid, but it was deallocated when mermaid
went out of scope. This example may seem contrived, but it can easily happen in real life such as when you use closures to run something much later, such as after an asynchronous network call has finished.
Change the greetingMaker
variable in WWDCGreeting
to look like this:
lazy var greetingMaker: () -> String = { [weak self] in return "Hello \(self?.who)." } |
There are two changes you made to the original greetingMaker
closure. First, you replaced unowned
with weak
. Second, since self
became weak
, you needed to access the who
property with self?.who
.
The playground no longer crashes, but you get a curious result in the sidebar: "Hello, nil."
. Perhaps this is acceptable, but more often you want to do something completely different if the object is gone. Swift’s guard let
makes this easy.
Re-write the closure one last time, to look like this:
lazy var greetingMaker: () -> String = { [weak self] in guard let strongSelf = self else { return "No greeting available." } return "Hello \(strongSelf.who)." } |
The guard statement binds a new variable strongSelf
from weak self
. If self
is nil
, the closure returns "No greeting available."
. On the other hand, if self
is not nil
, strongSelf
makes a strong reference, so the object is guaranteed to live until the end of the closure.
This idiom, sometimes referred to as the strong-weak dance, is part of the Ray Wenderlich Swift Style Guide since it’s a robust pattern for handling this behavior in closures.
Finding Reference Cycles in Xcode 8
Now that you understand the principles of ARC, what reference cycles are and how to break them, it’s time to look at a real world example.
Download the starter project and open it in Xcode 8. It needs to be Xcode 8 (or newer) because Xcode 8 added some fun new features that you’re going to use.
Build and run the project, and you’ll see the following:
This is a simple contacts app. Feel free to tap on a contact to get more information or add contacts using the + button on the top right.
Have a look at the code:
ContactsTableViewController
: shows all theContact
objects from the database.DetailViewController
: shows the details for a certainContact
object.NewContactViewController<
: allows the user to add a new contact.ContactTableViewCell
: a custom table view cell to show the details of aContact
object.Contact
: the model for a contact in the database.Number
: the model for a phone number.
There is, however, something horribly wrong with the project: buried in there is a reference cycle. Your user won’t notice the issue for quite some time since the leaking objects are small — and their size makes it even harder to trace. Luckily, Xcode 8 has a new built-in tool to help you find even the smallest leaks.
Build and run the app again. Delete three or four contacts by swiping their cells to the left and tapping delete. They appear the have disappeared completely, right?
While the app is still running, move over to the bottom of Xcode and click the Debug Memory Graph button:
Observe the new kind of issues (warnings, errors, etc) in Xcode 8: Runtime Issues. They look like a purple square with a white exclamation mark inside, such as the one shown selected in this screenshot:
In the Navigator, select one of the problematic Contact
objects. The cycle is clearly visible: Contact
and Number
objects keep each other alive by referencing one another.
These type of graphs are a sign for you to look into your code. Consider that a Contact
can exist without a Number
, but a Number
should not exist without a Contact
. How would you solve the cycle? Should the reference from Contact
to Number
or the one from Number
to Contact
be weak or unowned?
Give it your best shot first, then take a look below if you need help!
Bonus: Cycles with Value Types and Reference Types
Swift types can be categorized as reference types, like classes, and value types, like structures or enumerations. The major difference is that value types are copied when they are passed around, whereas reference types share a single copy of referenced information.
Does this mean that you can't have cycles with value types? Yes: everything is copied with value types, and thus no cyclical relationships can exist since no real references are created. You need at least two references to make a cycle.
Back in your playground, add the following at the end:
struct Node { // Error var payload = 0 var next: Node? = nil } |
Hmm, the compiler's not happy. A struct
(value type) cannot be recursive or use an instance of itself. Otherwise, a struct
of this type would have an infinite size. Change it to a class like so:
class Node { var payload = 0 var next: Node? = nil } |
Self reference is not an issue for classes (i.e. reference types), so the compiler error disappears.
Now, add this to your playground:
class Person { var name: String var friends: [Person] = [] init(name: String) { self.name = name print("New person instance: \(name)") } deinit { print("Person instance \(name) is being deallocated") } } do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(bert) // Not deallocated bert.friends.append(ernie) // Not deallocated } |
Here’s an example of a mixture of value types and reference types that form a reference cycle.
ernie
and bert
stay alive by keeping a reference to each other in their friends
array, although the array itself is a value type. Make the array unowned
; Xcode will show an error: unowned
only applies to class types.
To break the cycle here, you’ll have to create a generic wrapper object and use it to add instances to the array. If you don’t know what generics are or how to use them, check out the Introduction to Generics tutorial on this site.
Add the following above the definition of the Person
class:
class Unowned<T: AnyObject> { unowned var value: T init (_ value: T) { self.value = value } } |
Then, change the definition of the friends
property in Person
like so:
var friends: [Unowned<Person>] = [] |
And finally, change the do
block to look like this:
do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(Unowned(bert)) bert.friends.append(Unowned(ernie)) } |
ernie
and bert
now deallocate happily!
The friends
array isn’t a collection of Person
objects anymore, but instead a collection of Unowned
objects that serve as a wrapper for the Person
instances.
To access the Person
object within Unowned
use the value
property, like so:
let firstFriend = bert.friends.first?.value // get ernie |
Where to Go From Here?
Here’s the download of the complete playground and the Xcode project from this tutorial.
You now have a good understanding of memory management in Swift and know how ARC works. If you want to learn more about the new debug tools in Xcode 8, I suggest watching this WWDC session.
If you want an even more in-depth look at how weak references are implemented in Swift, check out Mike Ash’s blog post Swift Weak References. It covers how weak references in Swift differ from the Objective-C implementation, and how Swift actually keeps two counts under the hood: one for strong references and one for weak references.
Finally, if you are a RW subscriber, check out the iOS 10 Screencast: Memory Graph Debugger. It gives some great tips for getting the most out of the memory visualizer.
What do you think about the ARC approach? Let me know in the comments!
The post ARC and Memory Management in Swift appeared first on Ray Wenderlich.