Check out our Bootcamp Schedule View Schedule

Actually finding a memory management error with Instruments

Mark Dalrymple

Instruments is a very cool profiling application, but it’s one of those things that’s kind of hard to write about. You can outline the features it has, create some contrived debugging situations (“oh look, I’ve introduced a memory leak where no sane person could have accidentally created one.”), and make some pretty screen shots.

But then when you actually use it in the real world for solving a problem or coming to some interesting conclusion about the behavior of your system you typically don’t have the time to write about it. And when you finally have some writing time, you’ve already landed the fix to your source code and moved on with life. Then you have to re-introduce the bug, or ratchet back your source code control system when you want to write about it. Or as I’ll write about One Of These Days, the entire premise you were going to write about has changed due to implementation differences under the hood.

Luckily, last time I used Instruments I took screen shots, so here’s a quick example of actually finding a problem quickly with the tool.

The Scenario

A friend of mine is working on a product that’s oh-so-close to shipping, but it has a memory error. Memory grows when you go between two view controllers. You’d push one on to the stack, play with it, pop back, but the view controller was still hanging around in memory. The static analyzer didn’t complain about anything. Glancing at the code didn’t show any obvious memory management errors. Unfortunately this is a central part of the UI, so it’s not one of those “oops, there’s a memory leak in the About Box” situations where you can skip it and work on something more important.

“My memory usage grows” triggers my Pavlovian response of “run Instruments with the Leaks template”. Memory hanging around after it should be cleaned up calls for heapshots, one of my favorite Instruments features. Heapshots show you the objects that are still alive since the last time you took a heapshot. The usual pattern is get the app to a steady state, take a heapshot. Take another to make sure you’re not leaking when the app is idle. Then exercise the problem. Take another one to see who’s still hanging around in memory. Repeat until you understand what’s going on.

This time I was a little lazy. The original bug reporter is an amazingly smart guy who’s opinions I trust, and so I believed something really was not getting cleaned up. I just took a baseline, exercised the problem twice, and took another heap shot: (Click on images for full-sized versons in a new window / tab)


Two heap shots

I expanded the heapshot There are the two SWLGuestActionVCs I expected:


Heapshot Detail

The focus button (the little arrow in a circle thing) by one of the view controllers took me to a detailed memory management history, outlining every retain, release, and autorelease, along with the reference count:


Memory management details

Whoa. that’s a whole lot of stuff. Needless to say, I didn’t read all of it. I just skimmed down the right-hand column looking for something interesting. I just ignored anything with a leading UI because there’s a lot of activity moment-by-moment. After glancing down a couple of screenfulls, this timer jumped out at me:


4 timer dude sm

Ordinarily you don’t get timers for free. And from what I knew about the product, this particular view controller had a timer that drove updates.

It took about three minutes in instruments to get here. But now I actually have enough information to figure out what’s going wrong. Hint: it’s a retain cycle.

The timer is created in viewDidLoad:

    _refreshTimer = [[NSTimer scheduledTimerWithTimeInterval: 5.0
                              target: self
                              selector: @selector(refreshDisplayFromServer:)
                              userInfo: nil
                              repeats: YES] retain];

The retain really isn’t necessary because the runloop retains the timer until it’s invalidated, but doesn’t hurt anything so long as it’s balanced (which it is). The problem is this: the timer retains self, because that’s what it does. The view controller needs to release and invalidate the timer before it can finally get itself cleaned up, so there’s a “Goodbye Kiss” moment when the view controller knows it doesn’t need the timer, and then disposes of it. Otherwise the timer’s retain will keep the view controller alive until the end of time.

OK, the view controller needs to get rid of the timer. And indeed it has code to do that. The timer is cleaned up in viewDidUnload:

- (void) viewDidUnload {
    // release views and other things like good citizens

    [_refreshTimer invalidate];
    [_refreshTimer release];
    _refreshTimer = nil;

    [super viewDidUnload];
} // viewDidUnload

Looks OK to me. The problem is, viewDidUnload was not getting called. Setting a breakpoint confirmed that. viewDidUnload is documented as being called in low-memory situations, and this app is frugal with memory. But in general you can’t trust viewDidUnload for doing generic “view went away” work. The fix for this app is to dispose of the timer in viewDidDisapper:, since that will definitely get called as soon as the user pages back. And of course the timer creation code got moved to viewWillAppear:

But Doesn’t Instruments Detect Retain Cycles?

Instruments will detect retain cycles for you in some cases. I wrote a little app to make retain cycles, and Instruments detected them:


Retain cycle sm

It even has a cool little block-and-arrow diagram showing the relationship between the objects. You can find this under the Cycles & Roots option of the Leaks instrument. Unfortunately, the cycle caused by the timer was not picked up by Instruments. Probably because it’s not actually a leak – the timer still had a reference to the view controller.

That’s It

So what’s the point of this whole discussion? Basically, that it’s possible to use Instruments to find a problem quickly – it’s not just for big boil-the-ocean let-me-find-all-of-my-serious-performance-problems work. In this case, it was a little bit of information and a little bit of experience that pointed to a solution in under ten minutes. It’s worth the time to play around with Instruments and get familiar with the basic features of the different instruments and templates, and then actually use them when faced with problems to solve.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project