Fun with AutoRelease Pools in Cocoa Programming

xcode.jpg

I was talking to a friend today about the use of [[obj alloc] init] and the AutoRelease pools. Turns out, Apple recommends not to use them in iPhone applications. And that got us to dissecting them, to figure out why Apple wouldn't want this very handy feature used in iPhone apps.

First, we need to lay a few ground rules. When I have the code:

  MyObject*  obj = [[MyObject alloc] init];

the reference count on obj is set to 1. What really does this? Well, it's the alloc. Create it (alloc), and it's reference count is 1. Simple rule to remember.

So what if I want to create something, use it in my method, and then get rid of it? In these cases, you know the lifetime of the object is within the scope of your method, and in that case, you can choose to use the more efficient:

  MyObject*  obj = [[MyObject alloc] init];
 
  // ...use this guy for anything you need
 
  [obj release];

the last line in the code sample balances the alloc in the first line and reduces the reference count by one, and when zero, the object is removed. Easy. But this is very 'manual' - a lot like the C malloc() and free(). But it is efficient. So you just have to decide what's more important - efficiency or a little leeway on the object lifecycle?

For example, say we might want to keep this, but we might not. Then what? If we had written the code like this:

  MyObject*  obj = [[MyObject alloc] init];
 
  // ...use this guy for anything you need
 
  // if we have no error, keep this guy
  if (!error) {
      [self setObject:obj];
  } else {
      [obj release];
  }

Now the question becomes Can't we make this cleaner? and the answer is you bet we can:

  MyObject*  obj = [[MyObject alloc] init] autorelease];
 
  // ...use this guy for anything you need
 
  // if we have no error, keep this guy
  if (!error) {
      [self setObject:obj];
  }

with the addition of the autorelease call, we have placed this instance into an NSAutoreleasePool and at the end of the next run loop, it will clear out the pool, calling release on each item in the pool, and those whose retain counts hit zero are going to be freed.

Why is this better? Well... because when we create it, we can put it in the state that says "Hey, if I don't do anything with this guy, clean him up, but leave me the option to keep him." This, of course, is not as efficient as the first case, but this is far more flexible. If there are times when you might need to keep ahold of something you've created, this is the cleaner solution.

There's also the question of the Factory Pattern. If I'm making things for (possible) consumption, then I need to autorelease them before I return them. This allows the caller to either retain them or not, and if not, then at the end of the next run loop, the NSAutoreleasePool will clear it out and be done with it.

Remember, calling autorelease doesn't change the retain count - it just puts it in a pool that eventually will release the object and then possibly free it. So, it's in the definition of eventually that the inefficiency comes it.

In general, it's a decent idea to do [[[obj alloc] init] autorelease] if you're making a few dozen things at any one time. The NSAutoreleasePool doesn't get too bug, and it's cleaned up easily enough. Also, it gives you the option of having the same initializer code for those things that might stick around, and for those that won't. But make no mistake, it's not as efficient as not using it and careful scoping of the instance lifetime.

This seems to be the point with Apple on the iPhone. It's not as efficient, and with all the things on that little guy, resources have got to be at a premium. So, if you can control the scope of your instances, then do the simpler [[obj alloc] init] with a release when you're done. But if you can't do that, then use autorelease. Just realize the impact to your application.

As a final note, here are some interesting examples of where autorelease helps. Imagine that we have the following code where we create one object, hold on to it, create another, and then message the first. This would be recognized by a C coder as the classic dangling pointer:

  MyObject*  obj = [[MyObject alloc] init];
 
  // ...use this guy for anything you need
 
  // hold on to him for a sec
  MyObject*  holder = first;
 
  [obj release];
 
  // make another - maybe with different parameters
  obj = [[MyObject alloc] init];
 
  // ...use this guy for anything you need
 
  // message the first - and it'll blow up
  [holder doSomething];
 
  [obj release];

this guy is going to blow up because the original instance is long gone, but the pointer is still being held in holder. This seems obvious, but there are tons of ways you can get yourself into this bind by managing the release yourself. You have to be very careful if you're not using autorelease.

But in the iPhone, you have to try.

[2/6] UPDATE: I knew there was a good reference on this topic. This is from an old Stepwise.com posting in 2005, but it's as true to day as then. It's got great coverage of all the rules. Certainly something to keep handy when you get a little confused about the topic.