Dely's Guide to Cocoa Memory Management
Note: The Cocoa API exists in an open-source form known as GNUStep. I will refer only to Cocoa to save my precious fingers; every example here should build verbatim with the GNUStep API (though I can not personally test it to make sure... volunteers?)
Note2: The kind folks in comp.lang.objective-c have pointed out several errors/misconceptions in this writeup. I will be fixing them throughout the week.
Note3: I fixed those errors a while back but apparently my definition of "garbage collection" strays a bit from the norm. In my defense, I do not believe that garbage collection by its definition needs to be automatic. With Cocoa the onus is on the programmer to both mark and sweep, but in the end you are still deferring memory management (to an extent) to the framework instead of doing it truly manually. But I digress.
When I first started teaching myself Cocoa programming, I ran into the most trouble (and sometimes still do) with memory management. It seems every beginning Cocoa programmer spends two months dealing with programs that either dump core constantly or leak memory like a sieve because they fail to understand the elegant and complicated Cocoa memory management model. This is my attempt to stop the pain.
Objective-C and Cocoa are so intertwined that one often mistakes Cocoa features for Objective-C features. In fact, I even thought about naming this node "Objective-C Memory Management", which would serve the purposes of 95% of the readers perfectly, but for the sake of accuracy I have not. (hopefully softlinks will deliver the message properly).
----- Stack and Heap Basics
Objective-C itself has a very simple memory-management model; since it is a superset of C, all C types are treated the same way as one would in C. You have your stack-allocated data types:
struct someStruct *
someFunction()
{
int i;
struct someStruct foo;
And your
heap-allocated data types:
struct someStruct * bar = (struct someStruct *)malloc(sizeof(struct someStruct));
return bar;
Stack-allocated types are freed when they fall out of scope, i. e. when the close bracket is encountered (unless they are declared static)
} // (goodbye stack-allocated data types, hope nobody tries to dereference a pointer to you)
whereas heap-allocated types must be
manually freed:
int main (void)
{
struct someStruct * bar = someFunction();
free(bar);
}
C++ adds
object types to C but uses the same
paradigm; objects can be
declared within the body of a
function and
allocated on the stack
someObjectType foo;
Or manually allocated and freed on the heap:
someObjectType * bar = new someObjectType;
delete bar;
Objective-C only implements heap-based objects, thus if one was using the lowest-level
base classes, he would have to manually allocate and free every object.
someObjectType * foo = [[someObjectType alloc ]init ];
// a possible shorthand is: id foo = [someObjectType new ];
[foo free ];
What probably happened some day is that a few coders at
NeXT (now part of
Apple) were thinking:
"Wow, this manual allocation and deallocation is such a royal pain in the ass. Let's implement a cracked-out form of memory management with reference counting and a garbage collector!"
And hence the
frustrated head-scratching of Cocoa programmers worldwide. But
fear not, young Cocoa
jedi! By the time you are finished with this node you should be well on your way to making full use of what is quite possible the most
elegant memory-management solution ever provided for a compiled language. </hyperbole>
----- Introduction to Reference Counting
The vast majority of Cocoa objects inherit the NSObject class (some inherit NSProxy, which has to do with distributed objects and other fun things, but explaining that would require getting into all of the other crazy Smalltalk-derived idiosyncracies of Objective-C, for which I do not currently have the time). For the purposes of memory management, NSObject provides four essential methods:
- (oneway void)release; // No I don't know what a "oneway void" is.
- (id)retain;
- (id)autorelease;
- (unsigned)retainCount;
And one instance variable
unsigned retainCount;
When using NSObject or any class
descended from it, you are expected never to manually call the Obj-C
-(void)dealloc
method. Instead, you define your relationship to an object in terms of your current interest in it, as expressed through its retainCount. When an object is first initialized:
NSObject * foo = [[NSObject alloc ] init ];
It gets a
retain count of 1. That means 1 piece of code is interested in it. This
reference counting is nice; let's say you've passed that object to another
procedure or another object:
[someOtherObject doSomethingWith:foo ];
and have decided to get rid of it yourself. In other languages, this can present a
quandary; you eventually want to free this object's memory once it's no longer needed, but can easily get lost in making sure that it is only freed at the point that no other piece of
code is using it. This generally leads to a lot of
bugs wherein a piece of code
dereferences a
pointer to an object that has already been freed (
core dump) or an object never gets freed and
memory gets leaked. With reference counting, this becomes much simpler. The other object or procedure to which you pass your object sends it a
retain message:
- doSomethingWith:(NSObject *)theObject
{
[theObject retain ];
doTerribleThingsWith(theObject);
And you send it a release message;
[foo release ];
By retaining the object, the called procedure
increments the object's retainCount, expressing its "interest". Your release message
decrements it. The object now has a
retain count of one, same as when you created it, but now the
onus is on the called procedure to release it;
thus it is out of your hands. When the called procedure is done, it sends the release message:
[theObject release ];
}
At which point the object's retainCount drops to 0, which
then and
only then causes the dealloc
message to be
called, wherein the object generally frees up any
instance variables that it has
dynamically allocated and then calls its
superclass to clean up any instance variables it
inherited (eventually the
base-class dealloc message gets called, which cleans up the static instance variables and the object's spot in the
runtime table).
----- Garbage Collection
But Dely, you cry, all this retaining and releasing is a real PITA. What if I need some more temporary objects?
That, young jedi, is where our friends NSAutoreleasePool and the autorelease method come into play. An NSAutoreleasePool's role is roughly equivalent to that of a "garbage collector", though it is not one technically. A garbage collector, as implemented in languages like Perl, Smalltalk, or Java, automatically frees up objects once they are no longer in use, generally using some sort of reference-counting algorithm to determine when it is safe to deallocate. Java programmers will attest that the garbage collector can be a fickle beast, as one has to make sure an object is not accidentally being referenced in a data structure somewhere, and hence never being freed. With Cocoa, the programmer is expected to explicitly declare that his object should receive such a treatment.
The first step in Cocoa garbage is to create an NSAutoreleasePool. When creating a Cocoa application in Project Builder, the creation and freeing of the primary pool are taken care of within the main routine in the line:
NSApplicationMain(argc, argv);
An
autorelease pool is usually allocated each time the application begins its
event loop, and freed at the end of the loop (this is taken care of by Cocoa's
AppKit). There are many times when you will have to initialize your own (for example, when writing a
command-line tool, or when detaching a new
thread), so here is the basic gist of it:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc ] init ];
< do stuff here >
[pool release ];
The purpose of autorelease is quite simple. Let's say you have an
API method that returns an object that will obviously be for temporary use, for example something that returns an
NSString object with the name of the current working
directory. The method will
initialize the object and add an extra autorelease call. The autorelease call adds a
pointer to the object to the highest NSAutoreleasePool (NSAutoreleasePools can be stacked, so the object is added to the most recently allocated pool). When the pool is released, it goes through its internal
array of pointers, sending a release message to each object. An object can be autoreleased many times depending on its usage, and it will have that many release calls made when the pool is freed. Thus autorelease is simply a
deferred release, and all of the temporary objects can be cleaned up in one fell swoop when the pool is released, instead of being manually released one by one.
Here's our method:
+ (NSString *)currentWorkingDir
{
char buf[256 ];
NSString * currentDir = [[[NSString alloc ] initWithCString:getcwd(&buf,(256 * sizeof(char))) ] autorelease ];
return currentDir;
}
You will notice that the method begins with a +. This establishes it as a
class method (otherwise known as a
factory method) instead of an
instance method; in another unique feature of Objective-C, this method is sent to a handler for the object's class instead of to a particular instance. Every
alloc method, for example, is a class method. This type of class method is known as a
convenience method, as it returns an obviously temporary object. The calling method would be like the following, except we normally wouldn't allocate an autorelease pool for such a small function. I've created one so I can step through what actually happens here:
- somefunction
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc ]init ]; // pool is created
NSString * my_directory = [someSystemClass currentWorkingDir ];
// NSString is initialized with a reference count of 1 and its pointer
// is added to the pool (see above method)
NSLog ("My directory is: %@",my_directory"); // Cocoa equivalent of an fprintf() to STDERR
[pool release ];
/* The pool sends a release message to every object in its table
causing NSString's reference count to be decremented to 0,
and thus causing it to be deallocated;
then the pool takes care of deallocating itself */
}
Let's say that you've gotten an object via a convenience method, but you want to keep that object around for longer; the solution is simple - just send it a
retain message like you usually would, incrementing its reference count by 1 so you can keep it alive for as long as you see fit. NSAutoreleasePools are also useful if you are in some sort of
loop that will allocate a whole
shitload of objects to serve some temporary function. For example, the following function takes a
buffer, and allocates an NSString for each
token in that buffer, and then sends that string off to be processed; eventually, all of those strings need to be freed, so it simply creates a temporary autorelease pool for that purpose:
- tokenizeBufferAndProcess:(char *)buffer
{
someSortaTokenChopper * choppa = [[someSortaTokenChopper alloc ] initWithBuffer:buffer ];
someSortaTokenParser * parser = [[someSortaTokenParser alloc ]init ];
NSString * currentToken;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc ]init ];
while ( [choppa tokensLeftInBuffer ] )
{
currentToken = [[[NSString alloc ] initWithCString:[choppa nextToken ] ] autorelease ];
[parser parseToken:currentToken ];
}
[pool release ];
[parser release ];
[choppa release ];
}
You could possibly have generated thousands of NSString tokens in this loop, yet they'll all be cleanly deallocated when your pool is released. I could have even autoreleased the parser and chopper objects as well, but that would obscure what I'm trying to demonstrate.
----- In Conclusion
That pretty much wraps up the essentials of Cocoa memory management. I hope you'll stay around for my next lesson:
How to impress folks with your knowledge of whiz-bang programming concepts while not having a single useful app to your name, save for a front-end to a perl script that leeches jpegs off of free pr0n sites
In all truth, it will probably be on cacheing your selectors to optimize frequent method calls (Objective-C is notoriously slow at message passing, another result of its Smalltalk heritage)