Source: Eight Steps to Mastering Cocoa Memory Management on the iPhone

-----------------------------------------------------------------

Every iPhone developer has experienced this at least once: random crashes, unintelligible stack traces, bad memory accesses, and — worst of all — no idea where these problems come from or how to fix them.

What follows is a step-by-step guide to understanding how memory management on the iPhone can go wrong and what to do about it. It will teach you the concepts and the tools needed to reduce your memory footprint and develop stable mobile applications.

  1. Check early, check often.

    Check for memory issues as often as your code changes. Making sure that your memory management works should be part of your regular testing activities. Once you accumulate memory leaks in different pieces of interacting codes, it will be very difficult to tell them apart. The necessary trust in your application will be replaced by a sense of fear to touch it, and regaining that trust will become anything but easy.

    It’s a bit like going to the dentist. The longer you delay your next visit, the more problems you will accumulate, and the longer your visit will ultimately take.

    If you have to clean up existing code, with memory leaks all over the place, try to test individual pieces of the code first and work your way up from there. Once you know that an isolated module works fine, you can integrate it back into the main project. It will help your motivation to know that at least some things work.

  2. Discover leaks at run time using Instruments.

    Instruments

    The most important tool to help you track memory leaks,Instruments, ships with the iPhone SDK. If you have never used it before, be sure to check out Matt Long’s foolproof tutorial on how to get started with Instruments.

    While running the Leaks performance tool, note that the debugger console and Instruments cannot be active at the same time. You will have to lauch the system console (/Applications/Utilities/Console.app) and open up the system.log file. Also make sure to enter your application name into the search box to filter the results.

    You should test your application extensively before assuming that it is anywhere close to leak-free. Don’t trust the Leaks tool blindly. It will only report a memory leak once all references to an object have been lost. If an object is no longer needed but there is still a reference to it, then this will not be reported — although it effectively constitutes a leak.

    To manually check for those situations, and gain additional trust in your application on the way, have a look at the ObjectAlloc track within Instruments. Filter for the classes you are interested in by typing a class name in the Category search box, and monitor the amount of instances that are currently living. When you pop a view controller off the navigation stack, for example, its instance count should drop back to zero, along with all the objects belonging exclusively to that controller.

    A simple way to spot problems is to dive deep into your application, and then navigate back to the initial screen. Repeat that process a couple of times and track how memory usage is growing by monitoring the graph in the ObjectAlloc track. While you are repeatedly switching back and forth between different screens, memory usage is expected to vary, but the maximum amount of allocated instances should remain constant. If this is not the case, then some references are not released properly.

  3. Discover leaks at compile time using static analysis.

    While Instruments is a great tool to analyze the behavior of your code dynamically, you should not rely on it exclusively. The best way to build trust in your code is to know it intimately. By understanding the flow of control, you can identify memory issues without even running the code. Often enough, memory leaks are actually quite obvious and result from the programmer’s impatience or lack of concentration. Such mistakes can be tracked by machines. The approach of letting a program check the semantics of your code during compile time goes by the name of static analysis.

    A great and freely available static analysis tool for Objective C ist the Clang Static Analyzer. It consists of a collection of command-line utilities, but these are actually very easy to use. Just download the latest build, extract it to a directory of your choice (e.g./usr/local/share/clang), add the directory to your $PATH environment variable, and runscan-build xcodebuild in your Xcode project directory.

    If you are getting an error, you should specify your full configuration settings:

    scan-build -k -V xcodebuild -configuration Debug -sdk iphonesimulator3.0

    Modify the -configuration and -sdk flags to match your build settings within Xcode.

    Once you got in running, it will automatically launch the scan-view utility and open a browser window to display the reports. As always, don’t trust the analyzer blindly. Some of the reported bugs are not actually bugs, but perfectly correct pieces of code. They are just slightly non-standard or too complex for the analyzer to understand. While it may be appropriate to refactor the code so that the analyzer stops reporting errors, sometimes it makes more sense to just leave things the way they are.

    If you are unsure whether what the analyzer suggests is correct or not, you can always launch Instruments to find out how your code really behaves. For example, we discovered with Instruments that the framework method

    +[NSURLCredential credentialWithUser:password:persistence:]

    did not adhere to the Cocoa naming conventions. It created an NSURLCredential object, but did not autorelease it. So we had to release it manually to prevent a memory leak, and live with the static analyzer showing us as a bug what was actually a bugfix.

    [Update: The bug has been resolved in the current firmware. The method now adheres to the naming conventions and behaves just as expected.]

  4. Tune your run-time settings.

    When you start a new Xcode project, your environment is not automatically set up in the most efficient way for debugging memory issues. While testing in the Debug configuration, you might want to set an additional environment variable to gain additional information during run time. In order to do that, hit Cmd+Alt+X, go to the Arguments tab, and add the following to the Variables to be set in the environment:

    NSZombieEnabled : YES
    MallocStackLogging : YES
    MallocScribble : YES

    This will enable you to catch bad memory accesses, sometimes (but not always) reported with the infamous error code EXC_BAD_ACCESS. Bad memory accesses occur when you try to touch an already deallocated object. This may happen, for example, when the object has been released too many times. With NSZombieEnabled, deallocated memory areas will be flagged so that when you try to access them after deallocation of the object, your debugger will be able to output some useful information. Without zombies, it can often prove really hard to spot where exactly an object was over-released.

    Say you an error message similar to the following:

    project(<pid>, <...>) malloc: *** error for object <address>:
         pointer being freed was not allocated

    You can now run

    shell malloc_history <pid> <address>

    in the debugger console to get a stack trace for each malloc allocation and deallocation performed for that particular block of memory. Remember to add a breakpoint inmalloc_error_break so that your debugger will actually stop upon encountering the error.

    Sometimes, you will still encounter bad memory accesses. Your debugger console will report errors preceded by *** malloc[...]. This is the time to Enable Guard Malloc in the Runmenu. Guard Malloc is a tool that is part of libgmalloc.dylib. Once activated, memory allocations are placed on their own virtual memory pages. This enables Guard Malloc to discover whenever an access outside the allocated region is attempted.

    Be aware that running Guard Malloc will slow down your application tremendously. This is not an option that should be checked by default, such as enabling zombies. Still, Guard Malloc might save your life if you encounter any of those nasty bad memory access bugs that you are unable to make sense of otherwise.

  5. Write smart code.

    Smart Code

    Many articles on memory management with Cocoa focus on the theory, on how to write good non-leaking code. They present the knowledge of writing leak-free code as the means to achieving leak-free applications. We believe that it is actually the other way around. Testing your application for leaks in fact leads to knowledge about how to write leak-free code, whereas good coding practices alone do not necessarily guarantee that your application will be leak-free. This is why we focused on the tools first. Know that your code works instead of believing that it works. The worst programmer can write better code by continuously testing it than could the best programmer lacking efficient means to check his code.

    Now, there are a couple of ways you can make your code more aware of what is actually going on in your application. For example, to prevent bad memory accesses, you might want to use a macro like the following:

    #define SafeRelease(object) \
         if (object != nil) {
         [object release];
         object = nil;
         } else {
         Log(@"SafeRelease on nil object");
         }
         

    An analogous SafeAutorelease macro will come in handy as well, so be sure to create it along the way. By replacing all release and autorelease calls in your code withSafeReleases and SafeAutoreleases, you can make sure that you never release an object that has already been deallocated. Moreover, you will receive a log message whenever you attempt to do so. Your Log macro could look like this:

    #ifdef DEBUG
         #define Log(message) \
         NSLog(message @" (in %s at line %d)", \
         __PRETTY_FUNCTION__, __LINE__);
         #else
         #define Log(message)
         #endif
         

    Here is a sample output of the SafeRelease macro:

    SafeRelease on nil object (in -[MyController update:] at line 25)

    This alone will of course not prevent all bad memory accesses. Time to make your code even smarter. Whenever you enter a method, that method should make all its assumptions explicit. For example, if your method takes an object as a parameter that is not supposed to be nil (presumably because you are going to call one of its methods), then it is a good idea to place an appropriate assertion at the beginning of your method:

    - (void)myMethod:(NSObject *)myObject {
         NSAssert(myObject != nil, @"myObject must not be nil");
         // ...
         }
         

    Of course, you might want to include other assertions as well, such as making sure that an array is not empty or that an object responds to a certain selector that you are going to perform on it. While these considerations are not directly related to memory management, they are of course equally important ingredients of smart code.

  6. Follow the Cocoa memory management principles.

    Now that you have the right tools at hand to gauge your success and code that is smart enough to check for its own errors, it might finally be appropriate to understand some of the more basic stuff. The definite place to go for understanding how Cocoa manages memory is the official Cocoa memory management guide.

    The most important Cocoa memory management principle is the ownership principle. It means that whoever fetches an object has to take responsibility for it:

    • You start using an object by calling retain on it or newalloc or copy on its class.
    • Once you are done with the object, you invoke SafeRelease or SafeAutorelease on it.

    In the first case, the reference count of the object gets incremented, and in the second case it gets decremented. Once the reference count is back at zero, the object is deallocated. In the case of autorelease pools, the same logic applies, only that the deallocation is delayed (an object gets only released once the pool is released).

    There is a really good introduction to reference counting in Scott Stevenson’s article onLearning Objective C if you are new to the principle of ownership in Cocoa.

    Many times you will want to invoke or implement a factory method to create an object. Such a method can either autorelease the object when returning it, or it can decide not to release it at all, such as the new and alloc class methods do. Cocoa choses the approach of Coding by Convention to make unambiguous in which way a method behaves.

    The naming convention is as follows: Any method whose name starts with alloc or new, or that contains copy, returns a new instance that needs to be released. All other methods return an autoreleased object.

    So, for example, while a string created using

    NSString *string = [[NSString alloc] init];

    does not need to be retained (you are already its owner by convention), a string created using a method that does not include the magic words allocnew or copy, such as

    NSString *string = [NSString stringWithFormat:"%d", myInteger]

    has been autoreleased and thus needs to be retained to claim its ownership.

  7. Beware of delegation and retain cycles.

    Retain cycle

    Once you get the hang of it, the Cocoa memory management principles do not seem so overly complicated after all. Sure, here and there you tend to forget a release from time to time (such as forgetting to release a new property in the dealloc method), but these mistakes are easily spotted by the static analyzer or later, when testing your application using Instruments.

    More subtle and more dangerous are situations where an object will survive forever because it is part of a retain cycle. The simplest retain cycle involves only two objects, with each one holding a reference to the other. If both call a release on each other only in their dealloc methods, these methods will never be called since there is no way for the reference count of either object to drop to zero. More complicated retain cycles might involve any number of objects, holding references to one another.

    Matt Gallagher has written an excellent guide on how to avoid retain cycles which we highly recommend reading. Now you might ask, how stupid would you have to be to end up with a retain cycle? It seems obvious that an object owned be some other object should not attempt to claim ownership of its parent on its part.

    But there are situations where a retain cycle is practically unavoidable, and sometimes it is even desirable. That is often the case with delegation. Have you ever invoked asetDelegate:self on an object you owned? Then you might as well have created a retain cycle. It all depends on how the class implemented the delegation. In Cocoa, the convention is that by default, delegates are not retained. For example, a UITableView object does not retain its data source or delegate, neither does a UIApplication object retain its delegate. These types of references are called weak references.

    If you are instead dealing with strong references, then the delegate is retained, and asetDelegate:nil becomes necessary to break the retain cycle. The golden rule is that you should always go for weak references if you can make sure that the delegate object lives long enough for you to access it. Only if there is no way for you to make sure the object exists long enough should you implement a strong reference and retain the delegate. Make it obvious by documenting your code appropriately that the delegate has to be set to nilwhen the delegate reference is no longer needed.

  8. Free unused memory.

    The last advice may sound like the least critical one, but it is still vital to the user acceptance of your application. An app that is absolutely leak-free and devoid of bugs, but that uses an insane amount of memory, might be useless in practice. The iPhone 3G for example has only about 128 MB of RAM, of which about half should be reserved for operating system processes and background tasks. Once your application eats up too much of the remaining memory, it will be sent a memory warning by the system. And if your app does not respond to the memory warning in an appropriate way by freeing some of the allocated RAM, it will be killed without further ado.

    To prevent that from happening, you should handle memory warnings in your application delegate by implementing the following method:

    - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
         Log(@"Application received memory warning");
         // Release some objects that are not currently in use
         }
         

    So what are good candidates for objects ready to be released?

    • UIImages which are not being displayed,
    • NSData objects which are not currently in use,
    • other objects containing huge chunks of data, such as media objects.

    Typically, you will have a bunch of data that you only want to load into RAM when needed. Such data should be stored in a cache, such as in your NSTemporaryDirectory(). Now imagine you want to display an image. If it has not yet been loaded into memory, you want to load it from the cache. You might implement the relevant accessor in the following way:

    - (UIImage *)image {
         if (image == nil) {
         // Load the image from the cache
         }
         return image;
         }
         

    Once you receive a memory warning or otherwise want to free up some of the used memory, just call a method to release the image again. Such a method may be as simple as:

    - (void)unloadImage {
         SafeRelease(image);
         }
         

    The SafeRelease macro sets the image reference back to nil.

Mastering iPhone memory management

Memory management on the iPhone needs some getting used to. Developing for a platform that has only a total of 128 MB of RAM requires careful manual tuning. We have shown you the tools necessary to build trust in your application and the way it handles memory, as well as the necessary concepts to make sure your code deserves that trust.

Now it is up to you. Give us your feedback. Were these instructions helpful to you? What have we missed, what would you add? Please share your experiences with us.