Objective-C Memory Management Essentials
上QQ阅读APP看书,第一时间看更新

Understanding the autorelease pool mechanism

When you first start developing for Cocoa (iOS or Mac OS) you quickly learn to follow the standard alloc, init, and (eventually) release cycles:

// Allocate and init
NSMutableDictionary *dictionary = [[NSDictionary alloc] init];

// Do something with dictionary
// ...

// Release
[dictionary release];

This is great until you discover the convenience of just doing the following:

// Allocate and init
NSMutableDictionary *dictionary = [NSDictionary dictionary];

// Do something with dictionary
// …

Let's look inside and see what actually happens:

NSMutableDictionary *dictionary = [[NSDictionary alloc] init];
return [dictionary autorelease];

This approach is called autorelease pools and they are a part of the Automated Reference Counting (ARC) memory management model used by the Cocoa platform.

The ARC compiler will autorelease any object for you, unless it's returned from a method that starts with new, alloc, init, copy, or mutableCopy in its name. As before, these objects are placed into an autorelease pool, but in order to introduce a new language construct, NSAutoreleasePool has been replaced by @autoreleasepool, a compiler directive. Even using ARC, we are still free to use autorelease messages to drain/create our pool at any time. It doesn't affect the compiler when implementing retain and release messages, but provides hints when it's safe to make autoreleased objects go out of scope.

Cocoa frameworks (Foundation Kit, Application Kit, and Core Data) have some suitable methods to handle basic classes that inherit from NSObject, as NSString, NSArray, NSDictionary, and many more. These methods quickly allocate, initialize, and return the created object for you, which will also be autoreleased without you worrying about it so much.

Note

Note that I really meant "without worrying so much", not "without worrying at all" because even with these handy frameworks that create and clear the object for you, there will be cases when you want to take more control and create additional autorelease pools yourself.

Basically, an autorelease pool stores objects and when it's drained, it just sends the object a release message. The NSAutoreleasePool class is used to support Cocoa's reference-counted memory management system.

Autorelease pools were made by Apple and have been part of the language itself since OS X 10.7. If a program references the NSAutoreleasePool class while in ARC mode, it's considered invalid and is rejected in the build phase. Instead, in ARC mode, you need to replace it with @autoreleasepool blocks, thus defining a region where an autorelease pool is valid, as you can see in the following code:

// Code in non-ARC mode NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
// Taking advantage of a local autorelease pool.
[myPool release];

In ARC mode, however, you should write:

@autoreleasepool {
    // Taking advantage of a local autorelease pool.
}

Even if you don't use ARC, you can take advantage of @autoreleasepool blocks that are far more effective than the NSAutoreleasePool class.

Opposite to an environment that uses garbage collection, in one with reference counting, every object that receives an autorelease message is placed into an NSAutoreleasePool object. This NSAutoreleasePool class is like a collection of these objects and goes one by one sending a release message when it's drained. It drains the pool when you're out of scope. Then, every object retain's count is decreased by 1. By using an autorelease as an alternative to a release message, you extend the object's lifetime, this time maybe even longer if the object is later retained or at least until the NSAutoreleasePool class is drained. If you put an object into the same pool more than once, for each time, it will receive a release message.

While into an environment with reference counting, Cocoa presumes there will always be an autorelease pool available; otherwise, objects that have received an autorelease message won't get released. This practice will leak memory and generate proper warning messages.

At the beginning of a cycle of the event loop, an autorelease pool is created by the Application Kit (one of the Cocoa frameworks, also known as AppKit). It provides code to create and interact with GUI, and it's drained at the end of this cycle, then every autoreleased object created when processing an event is just released. It means you don't need to create the pools yourself as the Application Kit does it for you. However, if there are many autoreleased objects created by your application, you should consider the creation of "local" autorelease pools; this is an advantage to avoid the peak memory footprint.

To create an NSAutoreleasePool object, you can use the regular alloc and init methods and use drain to dismiss it. A pool cannot be retained; the consequences of drain is like a deallocation, and it's very important to do so in the same context you created it.

Every thread has its own stack of autorelease pools. These stacks contain NSAutoreleasePool objects, which in turn contain autoreleased objects. Every new autoreleased object is placed on the top of the pool and every new pool is placed on the top of the stack. A pool is removed from a stack when it's drained. Before a thread is finished, it drains every autorelease pool on its stack. Despite the fact that an autorelease pool can be manually created and objects can be manually added to it, ARC still drains the pool automatically: you're not allowed to do it yourself.

To ensure that you don't have to worry about ownership, this is what ARC does: easily create autorelease pools, and make them temporarily handle the holding and releasing of autoreleased objects for you.