Cocoa Factory Classes and Autorelease

There is one additional mystery that I wanted to mention whilst thinking about Cocoa conventions and memory allocation. When you first start developing for Cocoa (iPhone or Mac OS) you quickly learn to follow the standard alloc, init and (eventually) release cycle:

// Allocate and init
NSMutableDictionary *dictionary = [[NSDictionary alloc] init];

// Do something with dictionary
// ...

// Release
[dictionary release];

This is great until you discover the convenience of just doing the following:

// Allocate and init
NSMutableDictionary *dictionary = [NSDictionary dictionary];

// Do something with dictionary
// ...

Since we are not calling a method that starts with alloc, new or copy we do not own the dictionary object that is returned to us so we do not need to release it. In fact we must not release it since it does not belong to us. It is not immediately obvious why this works and when you first start out you can even get away with not worrying why it works.

Class Factory Methods

The Cocoa frameworks provide these convenience factory methods for many of the basic classes such as NSArray, NSDictionary, NSString, NSColor and NSDate. They allocate and initialize the object for you and return the created object. The trick to remember is that these objects have been autoreleased for you. So for our previous example you can think of it doing the following:

NSMutableDictionary *dictionary = [[NSDictionary alloc] init];
return [dictionary autorelease];

These convenience methods have their place but especially on the iPhone it is worth thinking twice before using them. The issue is that all the objects allocated by these convenience methods are placed in the applications autorelease pool and remain there until the end of the current run loop. This can be an issue if you are allocating a lot of temporary objects as your applications memory footprint will keep increasing and may even crash as a result.

Consider an example with a loop iterating over a large set of data:

for (NSInteger index = 0; index < 10000; index++) {
  NSDate *date = [NSDate dateWithTimeIntervalSince1970:index];
  NSString *title = [NSString stringWithFormat:@"date = %@",date];
  NSLog(@"%@",title);
}

If we run this example using the ObjectAlloc Instrument you can see the memory footprint grow to a peak of around 1.41Mbyes and then drop back once the run loop completes.

To avoid this you can simply avoid using the factory methods and allocate and release the objects directly. However that can sometimes be a pain so as an alternative you can create and empty your own autorelease pool within the loop:

for (NSInteger index = 0; index < 10000; index++) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSDate *date = [NSDate dateWithTimeIntervalSince1970:index];
  NSString *title = [NSString stringWithFormat:@"date = %@",date];
  NSLog(@"%@",title);
  [pool drain];
}

The memory footprint does not now grow during the code loop - I will not bother to include the Instruments screen shot since it is basically now a flatline. Of course, allocating and draining the autorelease pool for every iteration of the loop is probably not optimal either. Doing it every N iterations where “N” is tuned for the particular situation would be better still.

Either way the main point is to understand how the Cocoa factory classes work and to make a sensible choice about when and how to use them.