Thursday, December 3, 2009

Leak Hunting in iPhone Code

Eventually, when you're closing in on the release candidate of the software you're working on, you're probably going to check your app for memory and performance issues.

Same for me. On my current iPhone project, we're getting closer to our first beta and I felt I had neglected checking my memory usage and whether there are any apparent memory leaks in my app. Firing up Instruments, Apple's tool for analyzing Mac and iPhone software in many different ways (Memory, Disk Access, Open GL, Leaks, Core Data to name just a few), I was pretty happy with what I saw: the Leak-Detection tool did only find a couple of minor places in the code where I was leaking memory.

The output of Instruments, though not very intuitive at first, helped me identify the places in my code where I was allocating and / or retaining objects which I never released when it was time for them to be released. After patching those holes only a couple of leaking places were left that did not trace back into application code but into system APIs. Good job, I thought.

Coming from a Java background where I never had to spent a lot of thoughts to memory management (unless, of course, the occasional image = null; System.gc(); calls), switching to a reference-counted environment on the iPhone seemed to easy to be true. And sure enough I was going to find out that things are slightly more involved than checking the Leak tool in Instruments!

My colleague discovered the issue when he kept playing the game for longer than I had ever done. At some point in time, loading one of the included mini games, the application just crashed. Memory was his first thought and I also felt like it was some problem with running out of memory. Fortunately I was able to reproduce the bug and could see memory usage increase in Instruments while navigating the game.

After some time of digging for an answer I discovered an issue that was not detected by the Leak tool: cyclical references. Here I retained an object representing my scene for it to render properly while active. Within that object I created another object, however, which was retaining the scene. Once the scene was removed from screen, deallocation did not take place since the retain count of the scene did not reach 0.

How did I catch that one? A good indicator is ever increasing memory usage within your application. Monitor that while running on the phone itself to get a more accurate picture of your memory's distribution between Open GL ES memory and "regular" memory. I tried to explicitly free all my textures by calling cocos2d's [[TextureMgr sharedTextureMgr] removeAllTextures] method. Since this did not free any Open GL memory at all, something was going on. Finally, I carefully tracked memory usage using the Memory instrument when taking well-defined steps within the app. Once I had discovered a point where memory did not go back to where it was supposed to be (in case all of the memory had been free'd), I added some logging to see whether the object's I wanted to be dealloc'ed really were disposed of. That step let to the discovery of the the leak - maybe this will help you find some in your code, too!

Here's some sample implementation code to highlight what was happening:



This code kind of looks ok at first but soon you'll notice, that MyScene is never going to be dealloc'ed unless you explicitly call its' release selector twice somewhere outside of this code (which is going to lead to other problems - so don't do this!).

Assume the following code to understand why this is happening:



As you will probably know from Apple's documentation, whenever you're using alloc / init to create an instance of a class, you've implicitly retained the object. You have to explicitly release the object to properly dispose of the object. Now pay attention to the object's retain count:

1. The call to [[MyScene alloc] init] gives you an implicit retain count of 1.
2. Additionally, MyObject retains the allocated scene thus increasing its' retain count to 2.
3. Once [scene release] is called, the retain count is reduced to 1.

As you can see, the retain count is not going down to 0 as it should because of the retain by MyObject. Since MyObject only releases its' reference to MyScene in dealloc, this is not going to happen unless you explicitly send the MyObject instance the release selector.

The easy fix: if MyObject only lives as long as the scene is living, just don't retain the scene. It's going to be alive as long as MyObject is alive so there is not going to be a problem.

If you think this is happening to you, too, make sure you log the retain count of the object in question at appropriate times (whenever you think this object should be dealloc'ed but isn't). You can do this with a single line of code:



Hope this helps you find the pesky cyclical memory leaks. When is there going to be an app for that, too?

Did you find any other common patterns of leaks that are not detected by Instruments? Share your findings in the comments! I'm surely going to be thankful but so are probably all the other readers / Google users.

No comments:

Post a Comment