Benedict's Soapbox

autorelease 101

I was reading through the iOS Application Programming Guide and found the following passage:

Objects released using the autorelease method stay in memory until you explicitly drain the current autorelease pool or until the next time around your event loop. Whenever possible, avoid using the autorelease method when you can instead use the release method to reclaim the memory occupied by the object immediately. If you must create moderate numbers of autoreleased objects, create a local autorelease pool and drain it periodically to reclaim the memory for those objects before the next event loop.

I think this statement is a little misleading as it implies that autorelease is a pitfall that need to be avoided. I see autorelease as an convenient system for simplifying common memory management issues. However, it is important to understand how autorelease works because otherwise it’s possible to get into a mess.

Memory Management Rules

Memory is a limited resource so must be carefully managed. Cocoa uses a reference counting system to manage memory usage.

Objects maintain a retainCount which is set to 1 when the object is created. When the retainCount reaches 0 the object is no longer needed and is removed from memory. If you own an object you are responsible for releasing it when you have finished using it. You own an object if:

Just think NARC - new, alloc, retain, copy.

There are two ways to release an object. Either send it a release message, or send it an autorelease message. Once you’ve finished reading this post I hope you’ll have a clearer understanding of when to use release and when to use autorelease.

Example 1: Good autorelease

Lets look at two versions of a method, one using release and the other using autorelease:

release version

-(IBAction)updateTimeLabel  
{  
    NSDate *now = [[NSDate alloc] init];  
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];  
    [dateFormatter setDateFormat:@"yyyy.MM.dd G 'at' HH:mm:ss zzz";];  
    NSString *labelText = [[NSString alloc] initWithFormat:@"The time is now:\n %@", [dateFormatter stringWithDate:now]];  
    [now release];  
    [dateFormatter release];  
    self.timeLabel.text = labelText;  
    [labelText release];  
}  

auotorelease version

-(IBAction)updateTimeLabel  
{  
    NSDate *now = [NSDate date];  
    NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];  
    [dateFormatter setDateFormat:@"yyyy.MM.dd G 'at' HH:mm:ss zzz"];  
    NSString *labelText = [NSString stringWithFormat:@"The time is now:\n %@", [dateFormatter stringWithDate:now];  
    self.timeLabel.text = labelText;  
}  

The autorelease version is superior to the release version for a number of reasons:

And the final point is that there’s an implicit autorelease in the first version! [dateFormatter stringWithDate:now]] returns an autoreleased string. There is no way to get an NSDateFormatter to return a string which isn’t autoreleased.

The Apple frameworks use autorelease a lot. This is because the use of autorelease is baked into in the Cocoa coding conventions; autorelease is everywhere so you better get used to it!

Example 2: Bad autorelease

Before getting to the example lets look at run loops and autorelease pools.

Here’s a 1000 foot view of an app:

1000 foot view of an app

An app receives events from various sources such as touch events and network connections. These events are organised into a queue. The app takes the first event in the queue, determines which section of code handles the event and runs that code. Once the event has been handled the process starts again with the next event in the queue. When the queue is empty the app simply waits for the next event. This mechanism is the run loop.

Autorelease pools provide a mechanism for deferring the releasing of an object. Object are added to the pool when they are sent an autorelease message. Draining an autorelease pool causes a release message to be sent to every object in the pool. Objects can be added to an autorelease pool more than once; i.e. the number of release messages sent to an object is equal to the number of autorelease messages which were sent to the object.

At the start of each cycle of the run loop an autorelease pool is created and at end of the cycle the pool is drained. The key to understanding autorelease is being aware of how many objects are being added to the autorelease pool within each cycle of the run loop.

The following example is based around NSXMLParser delegate methods. These methods will be called many times as the XML parser process the file. Each invocation will be performed in the same run loop cycle.

XML sample


<countries>  
    <country>  
        <name>Afghanistan</name>  
        <flag>http://example.com/countries/Afghanistan/flag.png</flag>  
    </country>  
    ...  
    <country>  
        <name>Zimbabwe</name>  
        <flag>http://example.com/countries/Zimbabwe/flag.png</flag>  
    </country>  
</countries>  

NSXMLParser delegate methods

//these methods are implied - use your imagination!  
@property(readwrite, copy, nonatomic) NSString *characterBuffer;  
-(NSString *)localPathForRemoteImageUrl:(NSURL *)imageUrl;

...

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict  
{    
    if ([@"flag" caseInsensitiveCompare:elementName] == NSOrderedSame)  
    {  
        self.characterBuffer = nil;  
    }  
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string  
{  
    self.characterBuffer = (self.characterBuffer == nil) ? string : [self.characterBuffer stringByAppendingString:string];  
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName  
{  
    if ([@"flag" caseInsensitiveCompare:elementName] == NSOrderedSame)  
    {  
        NSURL *imageUrl = [NSURL URLWithString:self.characterBuffer];  
        NSData *imageData = [NSData dataWithContentsOfUrl:imageUrl];  
        [imageData writeToFile:[self localPathForRemoteImageUrl:imageUrl] atomically:NO];  
    }  
}  

In example 1 only a few relatively small objects were added to the autorelease pool (i.e. an NSDate, NSDateFormatter and 2 NSStrings). In this example a lot of small objects (NSStrings and NSURLs) and larger objects (NSDatas) are added to the pool. In this case relying on the run loops autorelease pool is a bad idea and could potentially cause the app to crash. There are a few ways we could avoid this problem:

1. Do not use autorelease, use explicit release instead. This solution is fine for short methods, but does result in code that is harder to read, and harder to maintain (for the reasons outlined in example 1).

2. Create another autorelease pool and drain it manually. Creating additional autorelease pools is easy and is sometimes the only option. If, for example, we want to save the image as a JPEG we would need to use UIImageJPEGRepresentation(). UIImageJPEGRepresentation() returns an autoreleased object. To ensure the memory used is released right away we would make the following changes:

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName  
{  
    if ([@"flag" caseInsensitiveCompare:elementName] == NSOrderedSame)  
    {  
        NSAutoreleasePool *pool = [NSAutoreleasePool new];  
        NSURL *imageUrl = [NSURL URLWithString:self.characterBuffer];  
        NSData *imageData = [NSData dataWithContentsOfUrl:imageUrl];  
        NSData *jpegData = UIImageJPEGRepresentation ([UIImage imageWithData:imageData, 0.8);  
        [jpegData writeToFile:[self localPathForRemoteImageUrl:imageUrl] atomically:NO];  
        [pool drain];  
    }  
}  

Autorelease pools are created as stacks. When an object is sent an autorelease message it is added to the pool at the top of the stack. Be sure to drain a pool when you have finished with it otherwise the stack will never be full popped and objects in the bottom pool will never be released.

Conclusion

As a general rule it is perfectly safe to use autorelease. Using autorelease helps you write less code which is easier to maintain. Be aware of what objects are being added to the autorelease pool. Relying on the runloops autorelease pool only causes problems when lots of objects or large objects are being created.