Memory Management in Objective-C and Cocoa
Last week we did a simple introduction to the Objective-C programming language, defining and implementing a class named “MyClass”. This class was instantiated from a main function and it showed how to send messages to it.
Unfortunately, this simple example makes very poor use of memory and ends up leaking the instance created. This week we will expand this example to add code that manually manages our memory and avoids these kinds of problems.
Before we get started, something important to highlight is that since Objective-C 2.0, there are two mechanisms to manage memory: the traditional (present since Objective-C 1.x) and which consists in manual management through reference counting, or, using the new optional Garbage Collector. Enabling Garbage Collection in Objective-C is very simple, just add the -fobjc-gc flag to your compiler invocation and this will be enabled, taking care of claiming memory corresponding to objects that are not referenced from any other object.
The problem with using Garbage Collection (beyond the costs in terms of memory use) is that it’s not available on the iPhone. The rest of this article will be based on memory management through reference counting.
Reference Counting
The way reference counting works in Cocoa (the set of Frameworks for programming Mac OS X and the iPhone) consists in that every object that is derived from NSObject includes an integer number representing the number of objects that reference it.
This count starts with 1 when a new object is created (through the call to the alloc method) and must be decremented to 0 (using the release method) for it to be freed.
An object can increment the counter manually (through calls to the retain method), which is usually used to convey an interest in that a given object shall not be freed, as it’s being used. When this happens it is said that the objects is property of both.
When this happens, it’s necessary to determine who will be responsible for freeing this object. In Cocoa, the convention is that the object, method or function that creates an object will be responsible for freeing its memory. Within the context of last week’s example, we should modfiy the main function to call release on the object instance before it finishes, as shown below:
#import "MyClass.h"
int main()
{
MyClass* myInstance = [[MyClass alloc] initFromCoords:1 y:2];
[myInstance printCoords];
[myInstance release];
return 0;
}
Note that this code additionally joins the call to initFromCoords with alloc. Doing this is another Cocoa convention, in this case to allocate and initialize objects. The call to init methods would be the equivalent of calling a constructor.
Autorelease Pools
With this, we have seen a very common (and simple) flow to create and destroy objects. Although sticking to the convention of “who allocates releases” seems simple, there is extra nuance.
In practice, the following problem arises: suppose we have a function f() that must create and return an object with data. If we observe the convention, F() should be responsible for releasing the object, however, if we call release before the object is returned, f()’s caller will not have a chance to call retain before this object is freed, and the program will abort with an error when we try to use the freed object.
The solution to this challenge is to take advantage of the so-called object AutoreleasePools. The idea behind AutoreleasePools consists in defining a scope so that when this scope ends, all objects that should be freed later on shall be freed.
This brilliant idea solves the problem in the following way: first instantiate an AutoreleasePool object. When f() needs to return its object, instead of sending it a release message, we send an autorelease message. This decrements the reference count later, when the AutoreleasePool object is freed. This provides enough leeway to the new object’s life so that whoever needs to use it may call retain on it.
In concrete terms of code, we modify our main function like so:
#import "MyClass.h"
#import <Foundation/NSAutoreleasePool.h>
int main()
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
MyClass* myInstance = [[MyClass alloc] initFromCoords:1 y:2];
[myInstance printCoords];
[myInstance autorelease];
[pool release];
return 0;
}
While within the context of this example, using AutoreleasePools is not strictly needed, having an AutoreleasePool in the main function is very useful, as it ensures that all objects that receive an autorelease message will eventually be destroyed.
One last thing to note is that AutoreleasePools may be embedded, defining new autorelease scopes. This avoids having to keep in memory until the end of the program any object that have received an autorelease message.
This covers a large part of what Objective-C and Cocoa memory management is about. You can find more information in Apple’s official documentation.