Scoped Objects in Objective-C

Object Scope and Lifetime

In Objective-C the lifetime of an object is not governed by the scope in which it appears – it is managed manually by the programmer. – (id) retain, -(void) release, and – (id) autorelease are the methods we use to let the runtime know if we’re still interested in an object or not.

Other variables in Objective-C don’t have to be managed in this way. Structures can be declared and will cease to exist once they leave the lexical scope in which they’re defined. Other languages, such as C++, can have instances that do the same thing – when the scope of a C++ stack allocated object is exited it’s destructor is called. That behaviour can be quite handy in that it allows the programmer to see the lifetime of the object simply by looking at the brackets in which it’s defined. It also means the programmer is less likely to accidentally leak an object since the scope will clean it up anyway. But we can’t do that in Objective-C.

How To Do That In Objective-C

{
    NSObject *myObject KBScopeReleased = [[NSObject alloc] init];
    NSLog( @"%@", myObject );
} // myObject is sent a release message here.

How does it work? Well first we notice that there’s something special in the declaration of myObject. KBScopeReleased lets the compiler know that we want this instance to be sent a release message as it leaves the scope it is defined in. Which seems like magic but here’s all that is involved:

#define KBScopeReleased __attribute__((cleanup($kb_scopeReleaseObject)))

We use the __attribute__ feature of the GCC compiler to define a cleanup function. This cleanup function will be called when a variable leaves scope and will be passed a pointer to the variable. I’ve defined the cleanup function to be $kb_scopeReleaseObject and here’s what it looks like:

void $kb_scopeReleaseObject( id *scopeReleasedObject )
{
    [*scopeReleasedObject release];
    *scopeReleasedObject = nil;
}

You don’t even really need to set the object to nil at the end there but I do because I’m crazy that way.

“I’ve Already Got Autorelease So I Don’t Care”

Good point. Using scope released objects will keep your peak memory usage down – the objects are released immediately rather than waiting for the autorelease pool to be drained – but, in general, autorelease pools are perfectly fine and are a pattern that Cocoa developers are accustomed to. So let’s put two good things together and see what we come up with.

KBScopeAutoreleased();

Drop that at the top of your scope and anything autoreleased between it and the closing of the scope will be automaticaly autoreleased. That’s handy if you’ve got a loop and want to keep your memory overhead down. As my friend and Rogue Amoeba colleague points out – Autorelease Is Fast. So just dropping a KBScopeAutoreleased() at the top of a loop will keep your memory overhead down at a very tiny speed cost.

Here’s what KBScopeAutoreleased() looks like:

#define KBScopeAutoreleased()
       NSAutoreleasePool *$kb_autoreleasePool##__LINE__             KBScopeReleased =
         [[NSAutoreleasePool alloc] init]

There’s a bit of C Macro Voodoo in there to make sure the variable name is unique but otherwise all it does is allocate a new NSAutoreleasePool and fix it up so it’ll be released when it exists scope.

Garbage Collection

Under Garbage Collection this becomes less useful but you can still use this trick to keep peak memory consumption down by changing the definition of KBScopeAutoreleased to be:

#define KBScopeAutoreleased()
  NSAutoreleasePool *$kb_autoreleasePool##__LINE__
        __attribute__((cleanup($kb_scopeDrainAutoreleasePool))) =
        [[NSAutoreleasePool alloc] init]

void $kb_scopeDrainAutoreleasePool( NSAutoreleasePool *pool )
{
    [*pool drain];
}

With that in place the NSAutoreleasePool will be drained when exiting scope which will trigger a garbage collection cycle.

That’s It

Really, not much to it but it’s kind of cool. For fun sprinkle KBScopeAutoreleased() around your code and your peak memory usage will drop.

4 thoughts on “Scoped Objects in Objective-C”

  1. In 64bit ObjC, yes. Basically you need a runtime with the C++ and Objective-C exceptions handled the same way. If not the cleanup will be called for C++ exceptions and not Objective-C exceptions.

    There used to be a great comment here explaining all that … I’ll try to dig it up. :)

  2. This is a fantastic idea, really. It’s perfect for my IRC app which often gets incoming messages outside of any runloop events. I had to put an NSAutoreleasePool around the message-receiving methods, but this will make the footprint even smaller than it is, which is great!

Comments are closed.