Tuesday, October 27, 2009

Accessors and Memory Leaks

As my understanding of Cocoa memory management creeps forward, I occasionally discover new types of memory leaks in my own code. My latest find involves initializing instance variables using accessor methods.

Let's say I have a class MyViewController with a property theImage. In the interface file, I declare the variable and define it as a property:

UIImage *theImage;

...

@property (nonatomic, retain) UIImage *theImage;

In the implementation file, I use the synthesize directive to give me free accessor methods. Because I have the property set to retain the variable's object, I add a release to the dealloc method:

@synthesize theImage;
...

[theImage release];


At some point in one of the methods, I initialize theImage:


[self setTheImage: [[UIImage alloc] initWithContentsOfFile: filepath]];


Looks innocent enough, right? Trouble is, the image created by the alloc will never get deallocated.

The problem arises because each allocation of an object must be balanced by a release of that object. Likewise, each retain of an object must be countered by a release. In this case, a retain is being done when we set the property using setTheImage; that's because the property was declared with the retain option. No problem; that retain is countered by the release in the dealloc method. But the call to alloc isn't countered anywhere, which leaves us a UIImage instance that is never fully released.

There are several solutions:

1. Use temporary variables with alloc. The above code could be rewritten as:


UIImage *tempImage = [[UIImage alloc] initWithContentsOfFile: filepath];

[self setTheImage: tempImage];

[tempImage release];


Using tempImage gives us a way to balance the alloc with a release.

2. Use autorelease. Here's a less verbose way to plug the leak:


[self setTheImage: [[[UIImage alloc] initWithContentsOfFile: filepath]] autorelease];


The downside here is that autorelease relies on Cocoa's automatic memory management capabilities, which introduces a bit of overhead in the form of less efficient memory usage (because objects end up sticking around longer than they need to) and additional processing (because the cleanup process must run periodically when otherwise it wouldn't have to). Exactly how much this overhead ends up mattering is the subject of much debate.

3. Use a convenience method instead of alloc/init. In many cases, convenience methods are available that make your code even more streamlined while still preventing leaks:


[self setTheImage: [UIImage imageWithContentsOfFile: filepath]];


Behind the scenes, convenience methods allocate, initialize, and autorelease your object, so this code is effectively equivalent to the code in the autorelease example.

I have yet to decide my personal position on the use of autorelease, but I'm going to start making sure I use one of these three approaches when initializing properties.

No comments: