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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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.

testskillz

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:

ss2

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]