iOS Memory Leaks? Program Crashes? Start Here!

So, you want to write applications for the iPhone and/or iPad? I do! And, with a background in software engineering, Java, C++ and yes, even plain old C, I thought I’d take the plunge. As I’ve learned about Apple’s software, I’ve come to appreciate its overall design, its use of patterns, and its maturity.

But, don’t think you can forget the nitty-gritty details of pointers, memory management, and good programming practices. Those of you out there with experience in Java and Java’s garbage collection: take heed. The iPhone programming environment does not support garbage collection. This means you must understand the iOS memory management model.

Thus, we begin our exploration of iOS with the iOS memory management scheme. The rules for correct handling of objects’ memory are clear, but often, individual cases can cause confusion. Let’s start with the basic rule:

If you own an object, you are responsible for relinquishing ownership when you’re done with it. And, if you don’t own an object, you better not relinquish ownership. (This is like selling a car that you don’t own—somebody is going to be very unhappy when they find out you don’t have the pink slip.)

How do you own an object and how do you relinquish ownership?

You own an object when you’ve created it with a method that begins with alloc, new, copy or mutableCopy.

You can also own an object by sending it the retain message.

You relinquish ownership by sending the object either the release or autorelease message. You use autorelease when you need to relinquish ownership, but you don’t want the object to be released from memory just yet. This will be discussed in another post.

Behind the scenes, all objects have reference counts (called retain counts in iOS). When you create an object, its retain count is 1. When you send an object the retain message, its retain count increments by 1. When you send an object the release message, its retain count decrements by 1. If an object’s retain count becomes zero, the system deletes the object from memory.

If you fail to relinquish ownership of an object you own, its retain count won’t ever reach zero and the object is never deleted from memory. This creates a memory leak. The problem becomes worse if the programming error is repeated because the object is created over and over again without a subsequent release.

If you relinquish an object that you don’t own and the object’s retain count becomes zero prematurely, your application may crash when you attempt to send a message to an object that has been deleted from memory.

Let’s look at some examples. Our first example creates a mutable array (using collection class NSMutableArray) and adds three NSString objects to it with message addObject. We print the contents of the array and then release ownership.

NSMutableArray *projects = [[NSMutableArray alloc] init];
[projects addObject:@"My Project"];
[projects addObject:@"Your Project"];
[projects addObject:@"Joe's Project"];

NSLog(@"Projects = %@", projects);        // display
[projects release];                // relinquish ownership

Note that we’ve taken ownership of collection projects because we created it with method alloc. We then relinquish ownership (as we must) by sending it message release. Here is the output.

Projects=(
    My Project,
    Your Project,
    Joe's Project
)

This is straightforward. Now let’s add the following code (highlighted below). We assign variable myProject to the projects object at index 0 and display this object.

    NSLog(@"Projects=%@", projects);
    NSString * myProject = [projects objectAtIndex:0];
    NSLog(@"my project = %@", myProject);
    [projects release];

Here is the updated output.

Projects=(
    My Project,
    Your Project,
    Joe's Project
)
my project = My Project

Should we relinquish ownership of object myProject? No, we must not. We do not own the object pointed to by myProject. If we were to relinquish ownership, we would incorrectly decrement the reference count for the simple reason that the caller here does not own the object.

Well then, who owns the object pointed to by myProject? The answer is the projects collection is the owner. When you add an object to a collection, the collection takes ownership of that object. Then, when a collection class is deallocated, it relinquishes ownership of each object it contains. Similarly, if you remove an object from a collection, the collection relinquishes ownership. Simply accessing an object from a collection, however, (for example with message objectAtIndex) does not constitute acquiring ownership by the caller.

Let’s look at another example. Here we build NSString objects in a loop and store each one in the projects collection.

NSMutableArray *projects = [[NSMutableArray alloc] init];

for (int i = 0; i < 3; i++)
{
    NSString * temp = [[NSString alloc] 
               initWithFormat:@"Project%d", i];
    [projects addObject:temp];
}

NSLog(@"Projects=%@", projects);
[projects release];

Here is the output.

Projects=(
    Project0,
    Project1,
    Project2
)

Can you identify the problem here? Look at the for loop. We create an NSString object with message alloc, which means we have ownership of the object pointed to by temp. We then add the object to the collection. At this point, the object pointed to by temp has a retain count of 2—one because we created it using alloc and two because the collection claims ownership when we add the object to the collection.

When the collection is deallocated, the collection class sends a release message to each object in its collection. These objects will then all have a retain count of 1. But since we don’t ever (incorrectly) relinquish ownership, these objects are not properly released and the retain count never reaches zero. We have a memory leak!

The solution is to release each object after adding it to the collection.

for (int i = 0; i < 3; i++)
{
    NSString * temp = [[NSString alloc] 
               initWithFormat:@"Project%d", i];     // ownership
    [projects addObject:temp];
    [temp release];               // relinquish
}

Here is a final example that uses a variation of this code.

NSMutableArray *projects = [[NSMutableArray alloc] init];

for (int i = 0; i < 3; i++)
{
    NSString * temp =
               [NSString stringWithFormat:@"Project %d", i];
    [projects addObject:temp];
}

NSLog(@"Projects=%@", projects);
[projects release];

This time we’ve left off the release of temp inside the for loop. Is this correct? Let’s return to the original rule, which says if we create an object with a message that starts with alloc, new, copy, or mutableCopy, then we must claim ownership. Here, we create each NSString object with message stringWithFormat. In this case, we must not claim ownership and therefore, must not relinquish ownership with message release. The above code is correct.

In the next post, I’ll discuss message autorelease. I’ll explain how NSString can (for example) correctly manage the memory of objects it creates using convenience methods such as stringWithFormat. I’ll also show you how you can use autorelease to correctly manage your own objects.