MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Programming With Cocoa

Memory Management in Objective-C

07/27/2001

Today we are going to learn about a seemingly intimidating programming topic: memory management in Objective-C.

Memory management allows you to make sure that precious memory space isn't taken up by objects that no longer have a purpose in your application, and that objects that are still needed continue to exist. After an object has been created and you finish using it, it will need to be disposed of so that memory isn't taken up needlessly.

The goal of memory management is to keep your application running like a well-oiled machine. When an object becomes detached from an application's control without being "de-allocated," memory is wasted. This waste of memory is called a memory leak, and if you have too many memory leaks in a large application, your application will go from running at a clip to becoming a sluggish mess. If allowed to continue running indefinitely, the memory leaks in your application will take up more and more memory, and with the way virtual memory works in Mac OS X, this means your hard disk will be accessed more frequently as it picks up the memory spill-over. Accessing the disk can slow down your application, so memory management is a serious concern and it must be handled properly.

Different programming languages dispose of objects in different ways. Java, for instance, implements a memory management system known as automatic garbage collection. In this system, the developer does not need to worry about actively de-allocating unused objects, rather a hidden garbage collector checks each object to see whether or not it is being used by other objects. If the garbage collector decides that an object is not being used by other objects, it will de-allocate that object, freeing up the memory it once occupied.

Comment on this articleDo you have any other questions for Mike about memory management using the Objective-C language?
Post your comments

Also in Programming With Cocoa:

Understanding the NSTableView Class

Inside StYNCies, Part 2

Inside StYNCies

Objective-C uses another form of memory management known as "reference counting." In this system, each object is associated with a count of how many other objects are currently using it, but the reference counts of objects in an application must be actively managed by the developer. The idea is that when an object has a reference count of "0", no other objects are using it, and it can safely be de-allocated to free up memory space.

Both of these memory management schemes have advantages and disadvantages. Automatic garbage collection is easy on the developer because it's entirely automatic. However, this convenience comes at the expense of performance and efficiency. Reference counting on the the hand is very efficient and fast because there is no garbage collector working behind the scenes using up resources. The flip side of the coin is that the increased performance and efficiency of reference counting requires more work on the part of the developer. Fortunately, this is not difficult once you understand the rules to the game. The goal of this column is to teach you these rules -- how to control the reference count of an object using the methods of Cocoa -- to ensure that objects you expect to exist do exist, and objects that are no longer needed are properly disposed.

Cocoa's methods

Learning CocoaLearning Cocoa
By Apple Computer, Inc.
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

The following discussion will be centered on four methods defined in the class NSObject. (NSObject is Cocoa's root class -- the granddaddy of the classes.) These methods are alloc, release, autorelease, and retain. There are several other methods in NSObject that you may find useful in your application, so take some time to browse through the class documentation. Below is a table describing the effect each of these four methods has on objects.

Method Description
+alloc

Creates a new object (allocating the necessary memory) and returns it with a reference count of "1".

-release

Decreases the receiver's reference count by "1".

-autorelease

Adds the receiver to the application's autorelease pool, which will decrease the receiver's reference count by "1" -- at some point in the future.

-retain

Increases the receiver's reference count by "1".

Our tools

To help us understand the way memory is managed in Objective-C, we will employ the services of two tools. One of them is an application that will let us execute code to demonstrate examples of good and bad memory management. You can download this application's project folder here.

The application is called MemApp. Its interface is a single button labeled "Go!" that is connected to an action method of the application's controller class called goButton. The action method contains code that we will manipulate to test different memory management situations. Additionally, I have created a subclass of NSObject called AClass, which is the class we will instantiate in goButton. AClass does not add any additional methods or instance variables to those defined by NSObject; its purpose is to have a unique name that will stand out among a sea of class names. The utility of this will soon be evident.

ObjectAlloc

The second tool we will use is an application provided by Apple as a part of the Developer Tools installation. It is called ObjectAlloc, and can be found in the /Developer/Applications/ directory.

This application controls the execution of your applications, and creates a table of every class used by your application (and there are a lot of these). Each class is associated with three numbers. They are the current number of instances of a class that exist in memory, the peak number of instances of the class that simultaneously exist in memory, and finally, the total number of objects of that class that have been created since the application began executing.

To get object allocation information for a particular application, you have to run that application from within ObjectAlloc. When you first launch ObjectAlloc, you will be presented with a "Run" dialog box, asking you to choose an application for ObjectAlloc to run. Our application can be found within the MemApp project folder at the path /MemApp/build/MemApp/Contents/MacOS/MemApp -- where the first MemApp is the name of the project folder, the second MemApp is the name of the application bundle (this will have a generic application icon associated with it in the file listing), and the last MemApp is the application within the bundle. The last MemApp is the one we want to run (it is also the only one that ObjectAlloc will allow us to run). Select it and click the Open button to bring up ObjectAlloc's main window.

For our purposes we will select the "auto-sort" check box at the bottom of the window. This will sort the class list alphabetically -- when AClass appears in the list of classes, it will appear at the very top directly beneath the totals row. The set of large buttons at the top of the window controls the application execution.

To start the application, click the Start Task button, which is the first button on the left. To stop the execution of the application, click the same button again. When ObjectAlloc starts running, you will see classes appear on the list as they are instantiated by the application. The class of interest for our experiment is, of course, AClass, but it won't appear on the list until the button is pressed to instantiate AClass for the first time. Below is an image of the setup of MemApp and ObjectAlloc running side-by-side.

Screen shot.

Our plan here is to run the application and observe how many instances of AClass exist before, during, and after the execution of the button's action method. For our purposes, we're going to assume that once the action method has completed running we no longer have a need for any instances of AClass. Let's see how it works out.

The good and the bad

Our first example is a situation where we won't even attempt memory management -- all we are going to do is instantiate AClass using alloc and leave it at that. The implementation file Controller.m contains the single action method containing the following code:

- (IBAction)goButton:(id)sender
{
    AClass *ourObject;
    ourObject = [AClass alloc];
}

As you can see, all we are doing is declaring a variable type to AClass, and then assigning it to the new object created with alloc. Load the executable into ObjectAlloc and start the task (make sure "auto-sort" is selected at the bottom of the window). MemApp will load and open up its main window. Click the "Go!" button, and watch as ObjectAlloc updates its list of objects present in memory. AClass should appear at the top of the list.

We see here that current, peak, and total counts all register one instance of AClass present in memory. If you keep pressing "Go!", you will see each of these counts increment for each call of the method.

However, after the code has finished executing, instances of AClass still remain in memory despite the fact that we're finished using it. This is not good, for several reasons. For one, we're wasting memory -- it's a memory leak. A small one, but still there. But we have bigger problems. After leaving the scope of the method (the scope of a name or a variable is the part of the program where we can use that name or variable) where ourObject is declared, there is absolutely no way we can communicate with the variable or the object that it points to. Our objects are lost somewhere in the memory space of out application -- marooned and cast away.

If ourObject had been declared as an instance variable with a global scope with respect to the class, the situation would be better because we could still access the object that ourObject points to -- even if it is outside of the method where it was created. The moral of the story is to not lose track of your objects. If you create an object using alloc, you own that object and it is your repsonsibility to properly dispose of it when you are done with it. Make sure you get rid of it before you forget about it.

The way we get rid of an object we've created with alloc is to send it a release or autorelease message. Either of these two methods will decrease an object's reference count by one, and because ourObject only has a count of "1" (from alloc), that will make its count "0", marking it for de-allocation. Let's see now how we can use these two methods.

The fix

The solution to this problem is to destroy each instance we create before we lose contact with it. In the code above, this would mean sending ourObject a release message before leaving the scope of ourObject (the method in this case). Later on we'll see the proper place to release objects assigned to instance variables. What we need to do now is send each allocated object a release message when we decide we're finished with it. Let's go into MemApp's Controller.m implementation file and change the method goButton as seen in the code below:

- (IBAction)goButton:(id)sender
{
    AClass *ourObject;
    ourObject = [AClass alloc];

    // Do stuff with ourObject….

    [ourObject release];
    ourObject = nil;
}

In the second-to-last line, we send ourObject a release message, bringing its reference count down to "0". The last line of code serves two imoprtant purposes. One, it helps us keep track of which variables still point to objects and which don't -- sort of a bookkeeping convenience. Its second purpose is more practical in that it helps us avoid problems associated with inadvertently sending messages to objects that no longer exist.

Sending a message to a nonexistent object will crash your application with a signal 10 (SIGBUS) or signal 11 (SIGSEGV) while your application won't even flinch at messages sent to nil. If you find yourself getting these kinds of crashes, check to make sure you're not trying to use an object that doesn't exist. (I've found in my experimentation that attempting to write to a nonexistent object will produce a Signal 11 segmentation violation, while attempting to retreive data from a nonexistent object will result in a Signal 10 bus error).

Open up the MemApp project and add these lines of code into Controller.m, then compile the application. From ObjectAlloc, stop the previous run of MemApp and restart it (the freshly compiled version will then be run). Now press the "Go!" button and look at the numbers for AClass. Here is what I got:

Parameter # Objects
Current 0
Peak 1
Total 1

What we see here is that no instances of AClass remain in memory at the end of our method. This is an example of effective memory management. Now we'll look at another way Cocoa allows us to release objects that produces a little more flexibility.

autorelease

Another way we can decrease an object's reference count is by sending it an autorelease message. When you send an autorelease message, the receiver is not immediately freed from memory, as is the case with release. Rather, autorelease decreases the receiver's reference count by one at some point in the future. autorelease thus provides added flexibility when releasing an object and lets us use it a little bit longer before it is actually de-allocated.

autorelease works by making use of an object known as an "autorelease pool." Whenever you send an object an autorelease message, it is added to the autorelease pool. At the end of the event loop, this autorelease pool is released by the application object and then all of the objects contained within it are released. At the beginning of the next loop, a new pool is created and the process is repeated ad infinitum. For practical purposes, an autoreleased object is considered to be valid anywhere within the scope where it received the autorelease message.

For example, our button method from MemApp can be changed to incorporate autorelease, rather than release.

- (IBAction)goButton(id)sender
{
    AClass *ourObject;
    ourObject = [AClass alloc];
    [ourObject autorelease];

    // We can still use ourObject until 
    // the very end of this method!
}

When we run this code through ObjectAlloc, we find that the instance counts are exactly the same as we got before with release. This shouldn't surprise you because the object is released after the end of the method.

autorelease is especially useful when we have a method that creates and returns an object. In this situation, if we follow the rules, we have to make sure any object we create is destroyed before it's too late. Using release in this case won't work because the object we're trying to return will be destroyed before it can be returned.

- (NSString)releasedString
{
  NSString *string = [[NSString alloc] initWithString:@"The Sender of this message will never see this string…"];
  [string release]; // we have to do this or we'll get a memory leak
  return string; // but what are we returning?
}

The solution to this is to use autorelease which allows us to properly deal with objects that we create and own, while giving the sender of the message a chance to grab onto the returned object for its own use.

- (NSString)autoreleasedString
{
  NSString *string = [[NSString alloc] initWithString:@"The Sender of this message will never see this string…"];
  [string autorelease]; // we have to do this or we'll get a memory leak
  return string;
}

We can now return a string to the message sender and let our minds rest comfortably knowing that we've fulfilled our duty to prevent memory leaks. These last few examples provide a perfect place to jump into our next topic -- convenience constructors -- which fit into the mold of a method that creates and returns an autoreleased object.

Convenience constructors

By now you have probably noticed in the class documentation that most Cocoa classes have a set of class methods that are named in the format +className.... These special class methods are called "convenience constructors," and are used to create temporary objects. What I mean by temporary is that objects returned by a convenience constructor are assumed to be autoreleased, and are thus valid only within the method that uses the convenience constructor.

An example of one of the many convenience constructors available is the NSString method, +stringWithCString:. We can use this method instead of creating an NSString instance using the usual alloc/init process. For example, consider the following code that uses alloc and init:

NSString *string = [[NSString alloc] initWithCString:"Hello"];
[textField setStringValue:string];
[string release];

This can be shortened using a convenience constructor:

NSString *string = [NSString stringWithCString:"Hello"];
[textField setStringValue:string];
// don't need to release string since objects 
// returned by convenience constructors are 
// assumed to be autoreleased.

Convenience constructors also make it less cumbersome to nest messages:

[textField setStringValue:[NSString stringWithCString:"Hello]];

So, if you need a string (or any other object) to exist only within a method, it's often easier, cleaner, and more readable to use a convenience constructor.

If you want to set an instance variable using a convenience constructor, you have to send it a retain message so that it is not released when the autorelease pool is cleared out, like so:

// assume stringInstanceVariable exists
stringInstanceVariable = [[NSString stringWithCString:"Hello"] retain];

retain is a way to assess object ownership on objects that you didn't initially create, so this extends our rule of matching every alloc with a release or autorelease, and now we also have to match any retain messages with release or autorelease.

Find out more about memory management

In this column I have tried to cover the basics of memory management in Cocoa and Objective-C. There are several other excellent articles and references available that I strongly encourage you to read. These articles go into more detail, provide additional code examples, and cover various "traps" that you have to look out for. Below is a list of these references:

Well, that's it for now for memory management. I hope I have given you a good feeling for how reference counting and memory management works in Objective-C and Cocoa. If you're still a bit shaky, don't worry too much. In the next several columns we will be building an application where we will follow the rules of good memory management, so you will be able to see how we implement all of this in real code. In the next article, we will build the interface for this application and learn a few things about tables in interfaces and the NSTableView class.

Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.


Read more Programming With Cocoa columns.

Return to the Mac DevCenter.

Copyright © 2009 O'Reilly Media, Inc.