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.
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
Instruments Tutorial with Swift: Getting Started
40 mins
- Getting Started
- Time for Profiling
- Instrumenting
- Drilling Deep
- Offloading the Work
- Allocations, Allocations, Allocations
- Instrumenting Allocations
- Generation Analysis
- Simulating a Memory Warning
- Strong Reference Cycles
- Finding Persistent Objects
- Getting Visual
- Breaking That Cycle
- Where to Go From Here?
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.
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”.
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!
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.
Start the app from Xcode. As before, perform at least three searches to accumulate some data.
Now, activate the Visual Memory Debugger like this:
- Click Debug Memory Graph.
- Click the entry for
SearchResultsCollectionViewCell
. - 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.
- 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.
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
.
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!
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.