Advertisement

Learn Objective-C: Day 5

by

Welcome to part five of this series on Objective-C. Today we're going to look at memory management, an element of Objective-C (and many other languages) that tends to trip up newer programmers. Most scripting languages (such as PHP) take care of memory managaement automatically, but Objective-C requires that we are careful with our use of memory and manually create and release space for our objects.

It's good practice to keep track of how much memory your application is using, so you don't encounter any leaks or hog up the memory on the system. It's even more important on mobile systems such as the iPhone where memory is much more limited than on a desktop machine.

Two Approaches

In Objective-C there are two methods for managing memory, the first if reference counting and the second is garbage collection. You can think of them as manual and automatic, as reference counting is code added by the programmer and garbage collection is the system automatically managing our memory. An important note is that garbage collection does not work on the iPhone, which is why we will not look at how it works. If you should wish to program for the Mac, then it's worth looking at Apple's documentation to see how garbage collection works.

Reference Counting

So, how do we manage our memory in our apps? First of all, when do we use memory in our code? When you create an instance of a class (an object), memory is allocated and our object can now function correctly. Now one little object might not seem that great of a deal, but when your apps grow in size - it quickly becomes an enormous problem.

Let's look at an example, say we have some sort of drawing app and each shape the user draws is a separate object. If the user has drawn 100 shapes, then we have 100 objects sat in memory. Now let's say the user starts over and clears the screen, then draws another 100 objects. If we don't manage our memory properly, we're going to end up with 200 objects doing nothing more than hogging memory.

We counter this by reference counting. When we create a new object and use alloc, our objects have a retain count of 1. If we call retain on that object, the retain count is now 2 and so on. If we release the object, the retain count decrements back to 1. Whilst the retain count is non-zero, our object will stick around, but when the retain count hits zero, the system deallocates our object - freeing up the memory.

Syntax

There are various methods you can call that will have some effect on memory management. First of all, when you create an object using a method name that contains alloc, new or copy, you take ownership of that object. This is also true if you use the retain method on an object. Once you release, or autorelease (more on that later) an object, you no longer take ownership of that object or care what happens to it.

So, if we alloc an object like so;

myCarClass *car = [myCarClass alloc];

We are now responsible for the object car and we must manually release it later (or autorelease it). It's important to note that if you were to try and manually release an object that has been set to autorelease, the application would crash.

Since we created our object using alloc, our car object now has a retain count of 1, meaning it will not be deallocated. If were to also retain our object like so;

[car retain];

Then our retain count is now 2. So in order to get rid of the object, we need to release twice to set the retain count to 0. Since the retain count is now zero, the object will be deallocated.

Autorelease and Autorelease Pool's

When you've created a new XCode project, you may have noticed some code that appears by default which creates an autorelease pool, up until now you've ignored it - now we're going to see what it does and where to use it.

The code you're probably familiar with seeing by now should look like this;

 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

 [pool drain];

Note: if you refer to older documentation, you may see the last line as release, rather than drain, this is a newer addition to the language but essentially does the same thing.

By now you should be able to tell to some extent what the code above is doing; it is creating an instance of NSAutoReleasePool called pool, allocating memory for it and then initiating it using the init method.

When we send the autorelease message to an object, that object is then added to the inner most auto release pool (inner-most because pools can be nested within each other - more on that later). When the pool is sent the drain message, then all the objects sent the autorelease message are released, essentially autorelease is deferring the release until later.

This is useful because many methods that return an object, typically return an autoreleased object, which means we don't have to worry about the retain count of the object we just got given, nor do we have to release it, as it will be done later on.

Nested Autorelease Pool's

I spoke briefly before about the ability to nest autoreleased pools, but what use is that to us? Although there are several uses, one of the most common uses is to nest an autorelease pool inside a loop that uses temporary objects.

For example, if you have a loop that creates two temporary objects to do whatever it is you wish, if you set those two objects to autorelease, you can use them until the pool is sent the drain message and not have to worry about manually releasing to deallocate. Apple have a great example of when you'd use this kind of nested autorelease pool in their documentation;

void main()
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
    NSArray *args = [[NSProcessInfo processInfo] arguments];
	
    for (NSString *fileName in args) {
		
        NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
		
        NSError *error = nil;
        NSString *fileContents = [[[NSString alloc] initWithContentsOfFile:fileName
																  encoding:NSUTF8StringEncoding error:&error] autorelease];
		
        /* Process the string, creating and autoreleasing more objects. */
		
        [loopPool drain];
    }
	
    /* Do whatever cleanup is needed. */
    [pool drain];
	
    exit (EXIT_SUCCESS);
}

Source: mmAutoreleasePools

The above example has a bit more than we need, but the structure is there. As you can see, when the application is opened and main is loaded, an autorelease pool called pool is created. Meaning anything autoreleased before the pool is sent the drain message will be assigned to this autorelease pool, unless it is inside an autorelease pool inside this one (sorry if that sounds a little confusing).

Inside the loop, another autorelease pool is created called loopPool. This pool is drained inside the loop, so anything autoreleased inside the loop is released before the loop iterates (or ends).

The inner autorelease pool has absolutely no effect on the outer autorelease pool, you may nest as many autorelease pools as you need. If we used autorelease in the loop above, but did not have a separate autorelease pool, then all the objects we were creating would not be released until the end of main. So if the loop ran 100 times, we would have 100 objects hogging up memory that have yet to be released - bloating our application.

retainCount

Before we wrap up, let's look at something that could help make memory management an easier-to-swallow chapter. So far when we've created objects, we've remembered how many references an object has and so on - but we've never seen an actual number. For the purposes of education, there is a method we can use to see how many references an object has called retainCount. The way we print a retainCount for an object is like so;

NSLog(@"retainCount for car: %d", [car retainCount]);

retainCount returns an integer, so we use %d to display it in the console. There are rare instances (which we won't go in to) where retainCount can be wrong and as such should not be 100% relied upon programatically. It is implemented for debugging only, so an app should never go live using the retainCount method.

Wrapping Up

Memory management is a subject that many new programmers find difficult, especially programmers who come from languages that take care of it all for you. We've covered the basics, which should be enough to allow you to find your feet and start incorporating memory management in to your apps.

Apple has a fantastic developer documentation library available on their developer website, which I highly recommend you check out if you're unclear on anything we touched on today. We've tried to keep the tutorial short and laser-focused today to help you understand memory management with no other fluff added.

Questions are welcome, as usual.

Challenge

Simply experiment with the console by creating a simple object that contains a few synthesized variables, create a few instances of this class and then check the retain count using the retainCount method. The best way to understand memory management is to fire up XCode and play around with alloc's and retain's etc, remember that crashes and mistakes aren't a brick wall as they'll ultimately help you avoid the mistakes in future.

Next Time

In the next installment we'll be looking at categories, a great feature available in Objective-C that can save developers a lot of time and make for more simplistic code.