The Autorelease Pool
An autorelease pool is an instance of the Cocoa class
NSAutoreleasePool. This pool is used to contain other Cocoa objects that were given
[autorelease] messages. Once stored in the pool, these objects remain valid until the pool itself is eliminated. The autorelease pool has a number of unique characteristics. For one thing, you can only dispose of the pool by explicitly sending it a
[release] message. Any attempts to send either a
[retain] or an
[autorelease] message to the pool generates an exception.
Another aspect of the pool is that you can only add objects to the pool. You cannot take objects out of the pool. Also, you cannot send another
[autorelease] message to an object that is already in the pool. If you do so, the pool generates an
EXC_BAD_ACCESS error. The
NSAutoreleasePool class is opaque; its internal structure is a black box. Short of using a low-level debugger, you have no way of telling how each autoreleased object is stored in the pool. Because of this opaqueness, you should only assume that all autoreleased objects would eventually be discarded. Never make any assumptions on how or in what order the disposal would occur.
When to Create Your Own Autorelease Pool
Cocoa applications that are based upon the Application Kit framework always have one autorelease pool available. This pool is created at the beginning of the event loop and is periodically eliminated at the end (see Figure 1).
Figure 1: The default autorelease pool.
In most situations, the default autorelease pool is sufficient. However, you can instantiate additional pools when necessary. Each instantiated pool is then arranged as a LIFO stack with the latest pool at the top and the default pool at the bottom (Figure 2). Any Cocoa object that received an
[autorelease] message would be added to the topmost pool.
Figure 2: The autorelease pool stack.
So when should you instantiate additional autorelease pools? Here are a number of design scenarios where instantiating additional pools would be useful:
- Building a Cocoa application that does not use the Application Kit framework; for example, a command-line utility that makes use of the Foundation Kit framework.
- A detached thread based on either
pthreadthat makes use of Cocoa objects.
- An application or thread that generates and disposes of a large number of autorelease objects.
In short, you need to instantiate at least one autorelease pool if you plan to have Cocoa objects without the benefit of the Application Kit framework.
Instantiating Autorelease Pools
Listing Ten shows how to instantiate and dispose of your autorelease pool. Apple recommends that the pool needs to be discarded in the same context in which it was instantiated. For example, if the pool is instantiated at the start of a loop, it needs to be eliminated at the end of that same loop. Also, if the pool is instantiated at the beginning of a method, it needs to be eliminated at the end of the method. Failing to dispose of the pool correctly prevents its cache of autorelease objects from being eliminated as well. This results in a potentially large memory leak.
NSAutoreleasePool *aPool; // instantiate the pool aPool = [[NSAutoreleasePool alloc] init]; /* Create and autorelease a lot of objects here */ // dispose the pool [aPool release];
You also have to make sure that you dispose of the autorelease pools in the same order in which they were instantiated. For example, if you have six autorelease pools instantiated, sending a
[release] message to pool 6 results in only that pool being discarded (see Figure 3).
Figure 3: Disposing autorelease pool 6.
However, if you send a
[release] message to pool 4 (Figure 4), pools 5 to 6 will also be discarded. If those pools still have objects with pending
retains, those objects will not get discarded.
Figure 4: Disposing autorelease pool 4.
A memory zone is a region of page-aligned memory used by a Cocoa application to store its Cocoa objects. Unlike the traditional application space, a memory zone is allowed to grow whenever necessary. Mac OS X creates a default memory zone for each Cocoa application. Cocoa objects that were instantiated using the
[new], [alloc], or
[copy] messages are allocated memory from the default zone. Although you do have the option of creating additional zones, the default zone is fast and efficient enough for most purposes.
There are scenarios where creating additional memory zones may be desirable. For instance, if a Cocoa object instantiates and destroys a lot of objects within its scope, the default zone could get severely fragmented (Figure 5); this results in a performance hit. Using independent memory zones for storing/disposing large numbers of objects prevents the default zone from being severely fragmented.
Figure 5: Fragmenting the default memory zone.
Another scenario occurs when the default memory zone has grown to at least two pages long. If two objects (say,
obj5) were instantiated, these objects may be located relatively far apart from each other in the default zone (Figure 6). This becomes an issue if a page fault occurs and
page2 is written to the disk.
Figure 6: When the default zone grows too large.
obj3 tries to access
obj5, a page fault has to occur, causing
obj5) to be read from the disk. If this page fault frequently occurs, it translates into a noticeable performance hit. A worse-case scenario would be where reading
page2 from the disk generates another page fault that may cause
page1 to be written to the disk. Creating a separate memory zone to store
obj5 keeps both objects within the same page. If a page fault occurs, the zone containing both objects is written to the disk. Also, when
obj3 is accessed, the resulting page fault causes the same zone (again, containing both objects) to be read from the disk.
One disadvantage of creating additional zones is that it increases the memory footprint of the application. You are also responsible for keeping track of any objects that were allocated to the zone, and making sure that the zone is empty before you dispose of it. Due to these disadvantages, creating additional zones should be considered only for optimization purposes.
To create your own memory zone, use the
NSZone *theZone = NSCreateZone(8192, 1024, TRUE);
This function takes three parameters: the initial size of the zone, the amount that the zone grows or shrinks, and whether the zone should reclaim memory. If it was able to create a new zone, it returns an opaque data structure called an
NSZone; otherwise, it returns a
When you instantiate a new Cocoa object, use the
[allocWithZone] message for the object to allocate its memory from my memory zone (Listing Eleven). Passing a
NIL to the same message would cause the object to use the default zone as its allocation source. To create copies of an object, use the
[mutableCopyWithZone] whenever appropriate.
NSZone *theZone = NSCreateZone(8192, 1024, FALSE); NSString *theStr, *theCopy; NSMutableString *theMutableCopy; // allocating an NSString from the custom zone theStr = [NSString allocWithZone:theZone]; [theStr init]; // copying NSString using the custom zone theCopy = [theStr copyWithZone:theZone]; // copying a mutable NSString using the custom zone theMutableCopy = [theStr mutableCopyWithZone:theZone];
You can also use the
NSRecycleZone function (Listing Twelve) to free up any unused memory contained by the zone. The function also preserves any objects stored in the zone that still have valid references.
NSZone *theZone = NSCreateZone(8192, 1024, FALSE); ... // allocate and dispose Cocoa objects ... // free all memory used by the zone NSRecycleZone(theZone); ... // destroy the zone completely malloc_destroy_zone(theZone);
Finally, if you want to completely dispose of the memory zone, use the system function call
malloc_destroy_zone(). This function frees up all memory contained by the zone and disposes of the zone as well. It does this regardless of whether the zone still has objects with valid references. It is your responsibility to ensure that you have discarded any objects you have stored in that zone before invoking
malloc_destroy_zone(). Never use
malloc_destroy_zone() to dispose of the default memory zone.
Jose is a engineering consultant in Vancouver, BC. He can be contacted at [email protected]