Autorelease is Not Your Friend

How many times have you written this line?

NSMutableArray *foo = [[[NSMutableArray alloc] init] autorelease];

At first glance, it looks fine. foo is an autoreleased NSMutableArray that you can use and, at the end of the method, it’s gone into the ether of the autorelease pool. Don’t get me wrong, most of the time, this use of -autorelease is acceptable. But, in this post, I’ll try to convince you to use autorelease differently in subtle ways.

Avoid Enormous Pools

Back to the first line of code. Let’s look at a real-world example of using -autorelease this way:

- (void)doSomeStuffWithAHugeArray:(NSArray *)array
{
    for (NSUInteger i = 0; i < [array count]; i++) {
        NSDictionary *aDict = (NSDictionary *)[array objectAtIndex:i];

        NSMutableArray *foo = [[[NSMutableArray alloc] init] autorelease];
        [foo addObject:[aDict objectForKey:@"someKey"]];

        // Some general processing of foo here.
    }
}

To the programmer, this appears perfectly valid. foo will be created once for each object in array and some processing will occur on it. You don’t need to worry about memory management since it’s autoreleased, right?

Right?

But what happens if there are 4 million items in this array? And you’re running on an iPhone? See, the problem with autorelease pools is that they drain their contents at some future point in the run loop. During your methods’ stack frame, the app never returns to the run loop. These objects keep getting added to the autorelease pool, and until the method is done, they don’t go anywhere. On the Mac this isn’t as much of an issue, since unused objects will get paged out of active memory, but on iOS, your app will be terminated when you run out of memory.

What to do? There are two approaches: first, implement a counter:

NSAutoreleasePool *pool = nil;
NSUInteger count = 0;

for (NSUInteger i = 0; i < [array count]; i++) {
    if (pool == nil) {
        pool = [[NSAutoreleasePool alloc] init];
    }

    static NSUInteger count = 0;
    NSDictionary *aDict = (NSDictionary *)[array objectAtIndex:i];

    NSMutableArray *foo = [[[NSMutableArray alloc] init] autorelease];
    [foo addObject:[aDict objectForKey:@"someKey"]];

    // Some general processing of foo here.

    count++;
    if (count % 1000 == 0) {
        // count is a multiple of 1,000
        [pool drain];
        pool = nil;
    }
}
[pool drain];

What have we gained, aside from a lot more code? Every 1,000 iterations through this loop, the autorelease pool that we’ve created drains its contents; this eliminates the possibility of millions of NSMutableArrays littering the contents of memory. In this case it’s unnecessary as we could just release foo at the end of the loop, but in most cases you’ll be dealing with other methods, either your own or those provided by Cocoa Touch or some library, that return autoreleased objects. This method prevents those objects from gumming up the works too much. As an example, if you’re processing JSON—let’s say Reddit comments—you can easily get millions of autoreleased NSString objects created, and significant processing of them can become unwieldy.

Treat -autorelease like -release

Here’s another typical pattern you see in Objective-C code:

MyAwesomeObject *bar = [[[MyAwesomeObject alloc] init] autorelease];
[NSThread detachNewThreadSelector:@selector(extraLongComputation:)
                         toTarget:self
                       withObject:bar];
bar.someProperty = 42;

That looks OK at first, but depending on how your app is running, you could be adding bar to an autorelease pool that is drained while -extraLongComputation is running. If you’re performing selectors after a delay or using an NSTimer, this is especially likely. In this case, I like to treat -autorelease like -release:

MyAwesomeObject *bar = [[MyAwesomeObject alloc] init];
[NSThread detachNewThreadSelector:@selector(extraLongComputation:)
                         toTarget:self
                       withObject:bar];
bar.someProperty = 42;
[bar autorelease];

By itself, this isn’t sufficient. However, when coupled with this code:

- (void)extraLongComputation:(MyAwesomeObject *)obj
{
    [obj retain];

    // TODO: write computation code.

    [obj release];
}

This ensures that bar survives long enough for your computation.

Nothing I’ve written here is a panacea, and I’m sure it’s all been written before (and probably better), but my hope is to at least encourage you to think more about the memory management of your application, lest you find yourself debugging odd crashes the night before a deadline.

Published by

Jeff Kelley

I make iOS apps for Detroit Labs.

One thought on “Autorelease is Not Your Friend”

  1. According to the documentation of [NSThread detachNewThreadSelector:toTarget:withObject:]:
    http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSThread_Class/Reference/Reference.html#//apple_ref/occ/clm/NSThread/detachNewThreadSelector:toTarget:withObject:

    The objects aTarget and anArgument are retained during the execution of the detached thread, then released.

    So it is safe to release it after you call “detachNewThreadSelector”, and in your “extraLongComputation” you don’t need to retain it.

    In any case, if what you were trying to prevent were true, in “extraLongComputation”, retaining it like you did there would not have been sufficient anyway, because there is the chance that your “obj” gets released before it gets to the “retain” line.

Comments are closed.