Instruments Tutorial with Swift: Getting Started

In this Xcode tutorial, you’ll learn how to use Instruments to profile and debug performance, memory and reference issues in your iOS apps. By Lea Marolt Sonnenschein.

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

Strong Reference Cycles

As mentioned earlier, a strong reference cycle occurs when two objects hold strong references to each other, preventing both from being deallocated. You can detect these cycles using the Allocations instrument in a different way.

Close Instruments and return to Xcode. Choose Product ▸ Profile, and select the Allocations template.

Allocations template

This time, you won’t be using generation analysis. Instead, you’ll look at the number of objects of different types hanging around in memory. Click the Record button to start this run. You’ll see a huge number of objects filling up the detail panel — too many to look through! To help narrow down only the objects of interest, type Instrument as a filter in the field in the bottom-left corner. This filters out all other values, except those related to your app, “InstrumentsTutorial”.

Allocations reference cycles

The two columns worth noting in Instruments are # Persistent and # Transient. The # Persistent column keeps a count of how many objects of each type currently exist in memory. The # Transient column shows the number of objects that existed but have since been deallocated. Persistent objects are using up memory; transient objects are not.

Finding Persistent Objects

You’ll see a persistent instance of ViewController. This makes sense because that’s the screen you’re currently looking at. There’s also an instance of the app’s AppDelegate.

Back to the app! Perform a search and look at the results. A bunch of extra objects are now showing up in Instruments: SearchResultsViewController and ImageCache, among others. The ViewController instance is still persistent, because it’s needed by its navigation controller.

Now tap the back button in the app. This pops SearchResultsViewController off the navigation stack so that it’s deallocated. But it’s still showing a # Persistent count of 1 in the Allocations summary! Why is it still there?

Try performing another two searches and tap the back button after each one. There are now three SearchResultsViewControllers?! Looks like you have a strong reference cycle!

Search results still visible

Your main clue in this situation is that not only is SearchResultsViewController persisting, but so are all the SearchResultsCollectionViewCells. It’s likely the reference cycle is between these two classes.

Thankfully, the Visual Memory Debugger introduced in Xcode 8 is a neat tool that can help you further diagnose memory leaks and retain cycles. The Visual Memory Debugger is not part of Xcode’s Instruments suite but is such a useful tool that it’s worth including in this tutorial. Cross-referencing insights from both the Allocations instrument and the Visual Memory Debugger is a powerful technique that can make your debugging workflow more effective.

Getting Visual

Quit Instruments.

Before starting the Visual Memory Debugger, enable Malloc Stack logging in the Xcode scheme editor like this: Option-Click InstrumentsTutorial at the top of the window (next to the stop button). In the pop-up that appears, click Run and switch to Diagnostics. Check the box that says Malloc Stack and select Live Allocations Only, and then click Close.

Visual Memory Debugger Scheme

Start the app from Xcode. As before, perform at least three searches to accumulate some data.

Now, activate the Visual Memory Debugger like this:

Memory Graph

  1. Click Debug Memory Graph.
  2. Click the entry for SearchResultsCollectionViewCell.
  3. You can click any object on the graph to view details in Inspector. There are multiple inspector panels, such as File, History, and Quick Help, where you can view more details.
  4. The most important one you want to see, though, is Memory Inspector.

The Visual Memory Debugger pauses your app and displays a visual, snapshot-in-time representation of objects in memory and the references between them.

As highlighted in the screenshot above, the Visual Memory Debugger displays the following information:

  • Heap contents (Debug navigator pane): This shows you the list of all types and instances allocated in memory at the moment you paused your app. Clicking a type unfolds the row to show you the separate instances of the type in memory.
  • Memory graph (main window): The main window shows a visual representation of objects in memory. The arrows between objects represent the references between them (strong and weak relationships).
  • Memory inspector (Utilities pane): This includes details such as the class name and hierarchy, and whether a reference is strong or weak.

Some rows in the Debug navigator have a number in parentheses next to them. The number indicates how many instances of that specific type exist in memory. In the screenshot above, you’ll see that after a handful of searches, the Visual Memory Debugger confirms the results you saw in the Allocations instrument. In other words, anywhere from 20 to — if you scrolled to the end of the search results — 60 SearchResultsCollectionViewCell instances for every SearchResultsViewController instance are retained in memory.

Use the arrow on the left side of the row to unfold the type and show each SearchResultsViewController instance in memory. Clicking an individual instance displays that instance and any references to it in the main window.

Visual Memory Debugger

Notice the arrows pointing to SearchResultsViewController. It looks like there are a few Swift closure contexts with references to the same view controller instance. Looks a little suspect, doesn’t it? Take a closer look. Select one of the arrows to display more information in the Utilities pane about the reference between one of these closure instances and SearchResultsViewController.

Visual Memory Debugger

In Memory Inspector, you can see the reference between Swift closure context and SearchResultsViewController is strong. If you select the reference between SearchResultsCollectionViewCell and Swift closure context, you’ll see this is marked strong as well. You can also see that the closure’s name: heartToggleHandler. A-ha! SearchResultsCollectionViewCell declares this!

Visual Memory Debugger

Select the instance of SearchResultsCollectionViewCell in the main window to show more information in Memory Inspector.

In the backtrace, you can see that the cell instance was initialized in collectionView(_:cellForItemAt:). When you hover over this row in the backtrace, a small arrow appears. Clicking the arrow takes you to this method in Xcode’s code editor.

In collectionView(_:cellForItemAt:), locate where each cell’s heartToggleHandler property is set. You’ll see the following lines of code:

resultsCell.heartToggleHandler = { _ in
  self.collectionView.reloadItems(at: [indexPath])
}

This closure handles when the user taps the heart button in a collection view cell. This is where the strong reference cycle lies, but it’s difficult to spot unless you’ve come across one before. Thanks to Visual Memory Debugger, you were able to follow the trail all the way to this piece of code!

The closure cell refers to the instance of SearchResultsViewController using self, which creates a strong reference. The closure captures self. Swift actually forces you to explicitly use the word self in closures, whereas you can usually drop it when referring to methods and properties of the current object. This helps you be more aware of the fact you’re capturing it. SearchResultsViewController also has a strong reference to the cells via its collection view.