Perhaps I’m showing my age, but I’m getting awful tired of language designers trying to improve on C/C++ memory management.
Just for review, here’s how memory management should work:
void foo() { // x is created on the stack. It is deallocated at the end of // the block/function and therefore its lifetime matches its // scope with no further effort. int x; // pX is a pointer to an int that the programmer creates with // new. By using "new", the programmer is taking responsibility // for freeing the memory used by pX before it goes out of scope. int *pX = new int(0); // ... interesting code goes here ... // The obligatory delete before we exit the block/function. delete pX; }
Everything else in C/C++ is a variation on this. You can put pointers and variables in structures and classes/objects but they follow the same rules: If you allocate with new, you must free with delete before you lose track of the memory (i.e. the one and only (or last remaining) pointer goes out of scope).
When we started coding for iOS, we ran into “manual retain/release” which is a variation on the C/C++ technique (or rather, a manual method of the automatic garbage collection used in Mac OS):
@interface bar { // Like C++, when the pointer is a member (instance) variable, // someone else is responsible for allocating memory for it. NSString * memberString; NSString * anotherString; } // But if the instance variable is accessible from the outside // world we can say it's a "property" and some of this is // managed for us. @property (retain) NSString * memberString; // Unless we don't specify "retain". Now we're responsible for // making sure the memory for anotherString is allocated and // freed. @property (assign) NSString * anotherString; @end @implementation bar - (void)foo { // These are the same. They're on the stack and are automatically // released when you exit the method/block. int x; // This is the equivalent of C/C++ "new", kind of. We can't just // do memory allocation without also initializing the object // (handled by new and the constructor in C++, but that's the // subject of a different article). The result is a pointer that // we're obligated to release before string goes out of scope. NSString * string = [[NSString alloc] init]; // Another way of doing the same thing, but this time the // resulting pointer is automatically released sometime in // the future that we don't care about. NSString * arString = [[[NSString alloc] init] autorelease]; // Yet another way of doing the same thing, but the autorelease // is done for us. We can tell because the method name starts // with something that looks like the name of the class but // without the prefix. Intuitively obvious, right? NSString * autoString = [[NSString alloc] stringWithUTF8String:"Automatically released"]; // Required release [string release]; } @end
And autorelease isn’t as automatic as you might think. You need to think about whether or not you need to create your own autorelease pool. This is important if you’re going to create a large number of autoreleased variables before returning to the run loop. You may want to manage your own autorelease pool in that case so you can free memory up at more convenient times.
If that’s not ridiculous enough, along comes Automatic Reference Counting (ARC) to “simplify” memory management.
@interface bar { // Like C++, when the pointer is a member (instance) variable, // someone else is responsible for allocating memory for it. NSString * memberString; NSString * anotherString; } // Instead of "retain", we create a "strong" reference. Memory // is freed when this particular instance variable goes out // of scope (is no longer accessible). @property (strong) NSString * memberString; // We use "weak" instead of "assign" to mean that we understand // someone else is in control of when this memory gets freed. @property (weak) NSString * anotherString; @end @implementation bar - (void)foo { // These are the same. They're on the stack and are automatically // released when you exit the method/block. In reality, they're // qualified with __strong by default. int x = 10; NSString * string = [[NSString alloc] init]; // could add __strong for clarity // You can also create weak pointers for some reason: NSString * __weak weakString; // Unfortunately, that introduces a bug into these lines of code: weakString = [[NSString alloc] initWithFormat:@"x = %d", x]; NSLog(@"weakString is '%@'", weakString); // In the code above, weakString is immediately deallocated after // it is created because there is no strong reference to it. See // how this is getting easier? // Not to mention: NSString * __autoreleasing arString; NSString * __unsafe_unretained uuString; // Now we don't have to do this: // [string release]; // And that's really all we saved by introducing "Automatic Reference Counting". // At the same time, we created a new way to introduce a bug by failing // to have any strong pointers to an object from one line of code to // the next. } @end
So we’ve gone from:
new / delete
to
retain / release (or autorelease with no release)
to
strong/__strong/weak/__weak/__autoreleasing/__unsafe_unretained
all in the interest of “simplification” and avoiding having to delete/release the memory we allocate. I frankly don’t see the benefits.