Memory Management in Cocoa

Author's Note: One of the major hurdles in learning to program with Objective-C is mastering memory management. If you have experience in Java or Perl, where memory is managed for you, the thought of managing things yourself can seem alien. If, on the other hand, you are coming from a C background, you'll find that Cocoa's memory management is a big step up from the simple malloc/free tools that you've been using.

This excerpt from Learning Cocoa with Objective-C gives a concise description of how memory management in Cocoa works, as well as a set of rules to help you manage things correctly. If you consistently apply these rules of thumb to your code, you will steer clear of problems.

Memory Management

Memory management is an important subject in programming. Quite a few of the problems encountered by novice application developers are caused by poor memory management. When an object is created and passed around among various "consumer" objects in an application, which object is responsible for disposing of it and when? If an object is not deallocated when it is no longer needed, memory leaks. If the object is deallocated too soon, problems may occur in other objects that assume its existence, and the application will most likely crash.

The Foundation framework defines a mechanism and a policy that ensures that objects are deallocated only when they are no longer needed. We have hinted at it before, but now it is time to explain things.

The policy is quite simple: you are responsible for disposing of all objects that you own. You own objects that you create, either by allocating or copying them. You also own (or share ownership in) objects that you retain. The flip side of this rule is that you should never release an object that you have not retained or created; doing so will free the object prematurely, resulting in bugs that are hard to track down, even though the fix is simple.

Object Initialization and Deallocation

As discussed in Chapter 3, Object-Oriented Programming with Objective-C (available in PDF format here), an object is usually created using the alloc method and is initialized using the init method (or a variant of the init method). When an array's init method is invoked, the method initializes the array's instance variables to default values and completes other startup tasks. For example:

NSArray * array = [[NSArray alloc] init];

When done with an object that you created, you send the release message to the object. If no other objects have registered an interest in the object, it will be deallocated and removed from memory.

When an object is deallocated, the dealloc method is invoked, giving the object an opportunity to release objects it has created, free allocated memory, and so on.

Reference Counting

To allow multiple objects to register interest in another object and yet have this object removed from memory when no other objects are interested in it, each object in Cocoa has an associated reference count. When you allocate or copy an object, its reference count is automatically set to 1. This indicates that the object is in use in one place. When you pass the object to other objects, wanting to make sure the object stays around for their use, they can use the retain method to increment the reference counter.

To visualize this, imagine that we have an object being held in three different arrays, as shown in Figure 4-5. Each array retains the object to make sure that it remains available for its use. Therefore, the object has a reference count of 3.

Figure 4-5. Reference counting

Whenever you are done with an object, you send a release message to decrement the reference count. When the reference count reaches 0, the release method will invoke the object's dealloc method that destroys the object. Figure 4-6 shows an object being removed progressively from a set of arrays. When it is no longer needed, its retain count is set to 0, and the object is deallocated.

Figure 4-6. Releasing of an object.

Autorelease Pools

According to the policy of disposing of all objects you create, if the owner of an object must release the object within its programmatic scope, how can the owner give that object to other objects? Or, said another way, how do you release an object you would like to return to the caller of a method? Once you return from a method, there's no way to go back and release the object.

The answer is provided by the autorelease method built into the NSObject class, in conjunction with the functionality of the NSAutoReleasePool class. The autorelease method marks the receiver for later release by an NSAutoreleasePool. This enables an object to live beyond the scope of the owning object so that other objects can use it. This mechanism explains why you have seen dozens of code examples that contain the following lines:

Each application puts in place at least one autorelease pool (for each thread of control that is running in the application) and can have many more. You put an object in the pool by sending the object an autorelease message. In the case of an application's event cycle, when code finishes executing and control returns to the application object, the application object sends a release message to the autorelease pool, and the pool sends a release message to each object it contains. Any object that reaches a reference count of 0 automatically deallocates itself.

When an object is used solely within the scope of a method that creates it, you can deallocate it immediately by sending it a release message. Otherwise, use the autorelease message for all objects you create and hand off to other objects so that they can choose whether to retain them.

You shouldn't release objects that you receive from other objects, unless you have first retained them for some reason. Doing so will cause their reference count to reach 0 prematurely, and the system will destroy the object, thinking that no other object depends on it. When objects that do depend on the destroyed object try to access it, the application will most likely crash. These kinds of bugs can be hard to track down, even though their cause and fix are simple.

You can assume that a received object remains valid within the method in which it was received and will remain valid for the event loop that is handling it. If you want to keep it as an instance variable, you should send it a retain message and then autorelease it when you are done using it.

Retaining Objects in Accessor Methods

One of the primary places where you will need to be aware of memory management is in the accessor methods of your classes. At first glance, it is obvious that you will want to release an old object reference and retain the new one. However, because code that calls a class's setter method might call it multiple times with the same object as an argument, the order in which you release and retain the object references is important.

As a rule, you want to retain the new object before releasing the old one. This ensures that everything works as anticipated, even if the new and old objects are the same. If you reverse these steps, and if the new and old objects are actually the same, the object might be removed permanently from memory before being retained.

There are other ways to ensure connections in setter methods, many of which are valid and appropriate for certain situations. However, this is the simplest possible pattern we can give that will always work. We will use this pattern throughout the book.

Rules of Thumb

The important things to remember about memory management in Cocoa distill down to these rules of thumb:

Objects created by alloc or copy have a retain count of 1.

Assume that objects obtained by any other method have a retain count of 1 and reside in the autorelease pool. If you want to keep it beyond the current scope of execution, then you must retain it.

When you add an object to a collection, it is retained. When you remove an object from a collection, it is released. Releasing a collection object (such as an NSArray) releases all objects stored in it as well.

Make sure that there are as many release or autorelease messages sent to objects as there are alloc, copy, mutableCopy, or retain messages sent. In other words, make sure that the code you write is balanced.

Retain, then release objects in setter methods.

NSString objects created using the @" . . . " construct are effectively constants in the program. Sending retain or release messages to them has no effect. This explains why we haven't been releasing the strings created with the @" . . . " construct.

If you apply these rules of thumb consistently and keep the retain counts of your objects balanced, you can manage memory in your applications effectively.

James Duncan Davidson
is a freelance author, software developer, and consultant focusing on Mac OS X, Java, XML, and open source technologies. He currently resides in San Francisco, California.