Like the Corners of My Mind

I just downloaded Xcode 4.2 on to my home machine. And one of the big new features, that I haven’t even had a chance to try out, is Automatic Reference Counting (ARC).

I find it strange to find myself feeling mildly conflicted about ARC, given that memory management in Objective-C was one of the early “yuck moments” when I was first playing with iPhone development. Now, Apple says we don’t need to manage our memory as fussily any more, and I should be happy. Thing is, I felt like I was just getting into the swing of it.

Way back in the mid-nineties, when shoulder pads started to become extinct and the cell phone had only just become smaller than your head, I used to program in C and C++. Back then, learning Java seemed like picking up an easier, friendlier C++ with training wheels. We’re all familiar with simple Java statements like this:

Date today = new Date();

This is a simple instantiation of a date with a default initial value (the moment in time that the line of code is executed). For me, part of what was interesting about this notation was just how similar it was to C++, where we’d see something like this:

IDate* today = new IDate();

In my misspent youth, I mostly coded C++ using AIX and an IBM class library. All of the IBM classes started with “I”. Other than that, the only real difference between the Java line and the C++ line is the little asterisk that indicated that “today” is a pointer to an IDate. Now, one of the simplifications that Java made over C++ was streamlining instantiation. In Java, the only real way to create objects is to use the “new” operator (let’s agree not to talk about reflection). In C++, there were two different ways to instantiate objects, depending on whether you just needed an object that was local to a particular method (i.e.: an object that lived in “stack” memory) or an object that might live longer than that (and would therefore exist in “dynamic” memory). In my example, above, I’m allocating new dynamic memory, typically on the heap. I’m allocating enough memory to hold an entire IDate object, and then I’m initializing that memory by calling the IDate constructor.

For C++, this was actually an improvement on the C way of doing things. In C, you’d write code like this:

time_t* today = (time_t*) malloc(sizeof(time_t));
time(today);

This example did the same thing — it allocated some memory (using the malloc function), returning a pointer to the newly assigned memory address. In this instance, I’m using the standard C time_t structure, so I need enough memory to hold a time_t (hence the “sizeof” call). But after I get that memory allocated, it could contain anything. It’s always essential to initialize any newly-allocated memory to some known values — hence, I call the “time()” function to initialize the memory to the current time.

C++ simplified memory allocation (to a point) by making the “memory allocation” stage and the “initialization” stage seem like one unit. The new operator allocated the memory, and used the class’s constructor to initialize that memory.

Unfortunately, the ugly side that we haven’t talked about is cleaning up that memory when you’re done with it. To free up dynamic memory in C, you need to use the “free” function:

time_t* today = (time_t*) malloc(sizeof(time_t));
time(today);

...

free(today);
today = NULL; /* always good practice */

Similarly, to free up dynamic memory in C++, you use the delete operator:

IDate* today = new IDate();

...

delete today;

The problem was always that some times it was hard to really figure out when you were done with memory. More on that later.

Now, Objective-C grew out of C in much the same way that C++ did: Objective-C and C++ were two divergent attempts to introduce object-oriented programming principles to C. But you can see how the same heuristics show up in Objective-C. Here we’d see something like this:

NSDate* today = [[NSDate alloc] init];

...

[today release];

Same kind of constructs. You allocate memory, and then you initialize the contents of that memory. When you’re done, you release the memory — never forget that memory must be freed up after you’re done with it.

In some ways, Objective-C is a bit more streamlined that C++ in that it only has one heuristic for instantiating objects — C++ has “stack” memory and “dynamic” memory. Objective-C doesn’t, as far as I can discern. But after 12 years of using Java’s automatic garbage collection, I must confess that it felt like a step backward in time to be forced to explicitly worry about freeing memory again.

Now, as I said above, it’s sometimes pretty difficult to keep track of when you’re done with a certain segment of memory. Say, for example, I instantiate a date, and I assign that date object to a bunch of properties of other objects. Now I can’t really free up my memory until each of those other objects has been deleted. Keeping track of the lifecycle of all your objects is serious business.

In C++, we quickly learned to adopt “smart pointers” or “reference counting pointers”. Smart pointers are simply classes that wrap a pointer, and which keep track of how many times you copy or use that pointer. The implementation details are kind of complicated to explain, but in practice, what you’d have is code that looked like this:

IDatePtr date = new IDate();

Or, at least that’s how it looked when I was using the IBM class library for AIX. Since those days, a new “standard class library” has emerged, and smart pointers are a common component of that class library. Unfortunately, the syntax is butt-ugly.

auto_ptr<IDate> today(new IDate());

Other than the fact that it’s kinda ugly, it does the same thing. If I always use smart pointers, I never have to explicitly call a delete function. Yay smart pointers! In fact, smart pointers are so common that essentially that’s the model of how Java works. When I say:

Date today = new Date();

The variable “today” isn’t really an object — it’s a reference to an object and references to the same object get counted. Any object that has no references is subject to garbage collection. Really, the only other difference in Java was that garbage collection happened asynchronously — you never really knew precisely when garbage collection was going to take place — whereas in C++, the deletion of objects was much more deterministic.

Objective-C has a feature that achieves a similar end, but it’s a bit more fussy to use. When you instantiate a object, if it’s going to be tricky to track when it should be released, you can use “autorelease”, like so:

NSDate* today = [[[NSDate alloc] init] autorelease];

Autorelease essentially makes use of a limited form of reference counting. I say “limited” because I still have to do a bit of work to ensure that I tell the object when I’m adding more references. The best practice is to use autorelease in conjunction with class properties. In Objective-C, we create class properties like so:

@interface Name : NSObject {
  NSString* firstName;
  NSString* lastName;
}

@property (retain) NSString* firstName;
@property (retain) NSString* lastName;
@end

and in the corresponding .m file:

@implementation Name

@synthesize firstName;
@synthesize lastName;

...
@end

The first reference to “firstName” can be considered equivalent to the instance variable declaration in Java. The @property essentially defines the signatures of the getter/setter pair. And the @synthesize reference can be thought of as a macro to implement the getter and setter. Because we’ve used the keyword “retain” in property definition, we are saying that we want reference counting to automatically take place any time we have a line of code like this:

myName.firstName = theFirstName;

We don’t see that reference counting is happening, but it is. It’s kinda magic.

Unfortunately, it’s too easy to be error-prone. Like, consider this method on my Name object:

-(void) assignFirstName:(NSString*) aFirstName andLastName:(NSString*) aLastName {
  // assign the first name
  self.firstName = aFirstName;
  // assign the last name
  lastName = aLastName;
}

Ooops. Do you see what I did? I assigned the last name parameter to the instance variable directly, and I bypassed the property setter. And, in so doing, I bypassed the reference counting mechanism that is magically provided by the almost-invisible setter. Ivor taught me a good coding practice to minimize the likelihood of this kind of error, but the point remains. In Objective-C, it’s still too easy to screw up memory management. To be fair, the same is true of C++. Which, frankly, was one of the original motivations behind the creation of the Java language.

Anyway, in the latest version of XCode, memory management is now no longer a concern for the developer. Or so they say; I haven’t had a chance to take it for a test drive, yet. But I’m surprised to find myself a little bit wistful, knowing that I’d just finally gotten my sea-legs under me again on memory management.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply