(Taking a short break from the Slow March Towards Your First Application, which we’ll resume in the next article…)
So, memory management.
Memory management isn’t something I actually think about much these days, at least, when doing Mac development. Objective-C 2.0 features garbage collection, and Python has had it for far longer – so PyObjC mostly took care of Objective-C 1.0’s memory for you.
However, sometimes you do Need To Know – if you want to write Obj-C 1.0 code to work with OS X 10.4 or ealier, or if you need fine control over memory use, say. There are other reasons, too, but take my word for it: it’s still useful to know about.
So here is How To Manage Memory Manually In Objective-C Without Garbage Collection, for those occasions.
Objective-C is reference-counted. I’m not going to explain refcounting (as I’m pretty sure everyone reading & caring about this article already knows it), just Obj-C’s terminology & rules for it.
Objects are alloced (created), retained (have their reference count increased) and released (have their reference count decreased) manually, by calling the appropriate [alloc](theClass), [retain](theObj) and [release](thObj) methods respectively. You must call these yourself; there are no C++esque ‘smart pointers’ or the like, no RAII to take care of it. Obviously, every alloc or retain must be balanced by a matching release, otherwise memory will leak. It’s pretty straightforward:
id someObject = [[SomeClass alloc] initWithParam: 42]; // refcount now 1
[someObject retain]; // refcount now 2
[someObject release]; // refcount now 1 again
[someObject release]; // refcount now 0, object gets destroyed
There are some less obvious tweaks to the system, though, which we’ll look at. Mostly, these revolve around autorelease. Let’s say you have a method that returns a new Kipper instance. Who manages the memory? Well, if you define getKipper the obvious way, like this:
- (Kipper*) getKipper
{
return [[Kipper alloc] initWithSomeState: [self getSomeState]];
}
It’s clear that the resulting Kipper will have a refcount of 1, and the calling code is responsible for releasing it when it’s done. This is tiresome, clutters up the client code, and if forgotten, causes leaks. We don’t want our client code to be a murky mess of manual memory management.
But if we release the Kipper inside getKipper, it’ll be destroyed before the calling method can use it.
Autorelease to the rescue. With autorelease, you define getKipper like this:
- (Kipper*) getKipper
{
return [[[Kipper alloc] initWithSomeState: [self getSomeState]] autorelease];
}
…and now the client need not release the Kipper by hand. This is simple, useful, and immensely confusing if you don’t know what’s going on – questions that might be coming to mind include “So how does that work then?” “If it’s so good, why aren’t all objects autoreleased?” and “How do you know if an object’s been autoreleased or not?”. It looks suspiciously like proper garbage collection, or perhaps magic. It’s actually neither, and very simple.
When called, autorelease does nothing to the instance itself, but just adds its address to a list. Later, Cocoa will walk that list, call release on every object in it, then empty the list. That is all.
So if you use the Kipper right away, and then forget about it, it’ll just get, well, automatically released. If you decide you need to keep it, you can of course just retain it; but then you’re responsible for calling release as well. Just like usual. There’s no real magic here: Autorelease and release are identical in effect and usage – except for the delay. Every alloc or retain must be matched by a release, it’s just that you can release it immediately, or ask Cocoa to release it later:
id someObject = [[SomeClass alloc] init]; // refcount 1
[someObject retain]; // refcount 2
[someObject retain]; // refcount 3
[someObject release]; // refcount 2
[someObject autorelease]; // refcount 2 (minus one deferred = 1)
[someObject autorelease]; // refcount 2 (minus two deferred = 0)
…at this point, the object’s references are “in balance” and the object is scheduled to be destroyed, and will be, automatically, when Cocoa sweeps that list of objects. But it’s still alive until Cocoa gets around to that, and you can still safely retain it again (with matching releases still required of course) to extend its life. Again, no magic here, the list will get processed, cleared, the refcount will decrease, but the extra reference you added manually will keep it alive:
[someObject retain]; // refcount 3 (minus two deferred = 1)
// let's pretend Cocoa clears that list now
// someObject's refcount is now 1, with nothing deferred
[someObject release]; // refcount 0, all gone, object destroyed
So when is that list cleared? The short answer, in Cocoa applications, is when your code has returned control to the NSApplication object.
If a method you’re writing is given an autoreleased object, you can safely use and abuse it until you return; you can even return it as your result. If you preserve the pointer anywhere else that lasts beyond the end of the function, though, you must retain it.
The long answer involves the mechanism behind autorelease: NSAutoreleasePool
See, each thread in your application has an autorelease stack. This is a list of autorelease pools, each of which is simply an array of NSObject pointers. When you autorelease something, it finds the top-most autorelease pool on the current thread’s stack (got that?), and adds the object to it. The objects all have their refcounts decreased when the autorelease pool itself is destroyed.
Autorelease pools take care of adding/removing themselves from the thread’s stack automatically when they’re constructed or destroyed. So Cocoa simply creates an autorelease pool at the top of the message processing loop, and destroys it at the end. This is how your functions’ objects get cleaned up when they return.
Why am I going into so much depth over an implementation detail? Because there are two circumstances where you need to do this manually:
If you create multiple threads in your application, those threads are responsible for creating and destroying their own autorelease pools. Only the main thread running the application’s message loop takes care of any pools itself.
If you have a long-running task that does a lot of work in a tight loop, and that work creates temporary objects, those objects will build up and not get released until the entire job is complete, and your function finally returns. In this case, you probably want to destroy and recreate your pool from time to time inside the loop to keep your working set down.
Finally, how can you know when an object is or isn’t autoreleased? Well, actually, only by naming conventions – although generally, they’re almost always autoreleased. You’re only responsible for releasing something if you’ve explicitly retained or alloced it yourself. Whenever an object is given to you by someone else, it’s their problem. This includes factory “convenience methods”, incidentally. So while [alloc]([NSString) initWithString: @"blah"]; needs to be released (you alloced it, you clean it up), [stringWithString: @"blah"](NSString); has been autoreleased.
The naming conventions: Any method whose name starts alloc or new, or containing copy, returns a new instance with a refcount of 1 that needs releasing. Anything else returns something autoreleased. You should follow these conventions in your own code, as well as expecting it in Cocoa’s.
It’s worth noting that generally (unless it states otherwise in the docs), a Cocoa structure will look after the refcounts of its members – this includes arrays, dictionaries, the view hierarchy, and so on. So if you receive an autoreleased object, stick it in an NSMutableArray, and remove it when you no longer care, you actually don’t need to manage its memory by hand at all.
Where this may bite you occasionally is if you’re moving objects from one array to another: If the array is holding the last remaining reference to the object, removing it will destroy the object, and then you’ll add an invalid pointer to the second array. And then you’ll die.
(Well, your code will. Unless memory-scribbler events have taken on some kind of “Ring”-style paranormal significance.)
Anyway, the solution is obvious: just add it to the second array before you remove it from the first. In the case of the view hierarchy, take an extra, temporary (and probably autoreleased) reference before removing the view from its parent view.
And now you can manage memory in Cocoa.