ARC and Memory Management in Swift
In this tutorial, you’ll learn how ARC works and how to code in Swift for optimal memory management. You’ll learn what reference cycles are, how to use the Xcode 10 visual debugger to discover them when they happen and how to break them using an example of a reference cycle in practice. By Mark Struzinski.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
ARC and Memory Management in Swift
30 mins
- Getting Started
- Is That in Scope?
- An Object’s Lifetime
- Reference Cycles
- Checking Your References
- Hold the Phone(s)
- Weak References
- Unowned References
- Who’s Your Carrier?
- Break the Chain
- Reference Cycles With Closures
- Capture Lists
- Capture Your Self
- Using Unowned With Care
- Disarming the Trap
- Now for Something Different
- Finding Reference Cycles in Xcode 10
- Where Is That Leak?
- Bonus: Cycles With Value Types and Reference Types
- Reference and Value
- Where to Go From Here?
Capture Your Self
Replace the declaration of completePhoneNumber
in CarrierSubscription
with the following:
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 you’ve captured self
as an unowned reference instead of a strong reference.
Build and run, and you’ll see CarrierSubscription
now gets deallocated. 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, you are creating a new self
variable, which shadows the existing self
variable only during the closure’s scope.
Using Unowned With Care
In your code, the relationship between self
and completePhoneNumber
is unowned
.
If you are sure that a referenced object from a closure will never deallocate, you can use unowned
. However, if it does deallocate, you are in trouble.
Add the following code to the end of MainViewController.swift:
class WWDCGreeting {
let who: String
init(who: String) {
self.who = who
}
lazy var greetingMaker: () -> String = { [unowned self] in
return "Hello \(self.who)."
}
}
Next, add the following code block to the end of runScenario()
:
let greetingMaker: () -> String
do {
let mermaid = WWDCGreeting(who: "caffeinated mermaid")
greetingMaker = mermaid.greetingMaker
}
print(greetingMaker()) // TRAP!
Build and run, and you’ll crash with something like the following in the console:
User John was initialized
Phone iPhone XS was initialized
CarrierSubscription TelBel is initialized
0032 31415926
Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated2019-02-24 12:29:40.744248-0600 Cycles[33489:5926466] Fatal error: Attempted to read an unowned reference but object 0x600000f0de30 was already deallocated
The app hit a runtime exception because the closure expected self.who
to still be valid, but you deallocated it when mermaid
went out of scope at the end of the do
block.
This example may seem contrived, but it happens in real life. An example would be when you use closures to run something much later, such as after an asynchronous network call has finished.
Disarming the Trap
Replace the greetingMaker
variable in WWDCGreeting
with the following:
lazy var greetingMaker: () -> String = { [weak self] in
return "Hello \(self?.who)."
}
Here, you’ve made two changes to the original greetingMaker
. First, you replaced unowned
with weak
. Second, since self
became weak
, you needed to access the who
property with self?.who
. You can ignore the Xcode warning; you’ll fix it shortly.
The app no longer crashes, but when you build and run, you get a curious result in the console: “Hello nil.”
Now for Something Different
Perhaps this result is acceptable in your situation, but more often, you’ll want to do something completely different if the object is gone. Swift’s guard let
makes this easy.
Replace the closure one last time with the following:
lazy var greetingMaker: () -> String = { [weak self] in
guard let self = self else {
return "No greeting available."
}
return "Hello \(self.who)."
}
The guard statement binds self
from weak self
. If self
is nil
, the closure returns “No greeting available.”
On the other hand, if self
is not nil
, it makes self
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.
Build and run to see that you now get the appropriate message.
Finding Reference Cycles in Xcode 10
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.
Open the Starter project inside the Contacts folder in Xcode.
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-hand side of the screen.
Have a look at the code:
-
ContactsTableViewController: Shows all the
Contact
objects from the database. -
DetailViewController: Shows the details for a certain
Contact
object. - NewContactViewController: Allows the user to add a new contact.
-
ContactTableViewCell: A custom table view cell that shows the details of a
Contact
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 the leak even harder to trace.
Fortunately, Xcode 10 has a 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 to have disappeared completely, right?
Where Is That Leak?
While the app is still running, move over to the bottom of Xcode and click the Debug Memory Graph button:
Observe the Runtime Issues in the Debug navigator. They are marked by purple squares with white exclamation marks inside, such as the one selected in this screenshot:
In the navigator, select one of the problematic Contact
objects. The cycle is clearly visible: The Contact
and Number
objects keep each other alive by referencing one another.
These issues 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 reference from Number
to Contact
be weak or unowned?
Give it your best shot first, then take a look below if you need help!
[spoiler]
There are 2 possible solutions: You can either make the relationship from Contact
to Number
weak, or you can make the relationship from Number
to Contact
unowned.
Apple’s Documentation recommends that a parent object should have a strong hold on a child object by convention — not the other way around. This means that giving Contact
a strong hold on a Number
, and Number
an unowned reference to a Contact
, is the most convenient solution:
class Number {
unowned var contact: Contact
// Other code...
}
class Contact {
var number: Number?
// Other code...
}
Run and debug again. You’ve resolved the issue!
[/spoiler]