Memory Management in Cocoa

Listen carefully: you must learn to manage your own memory.

Verily, I say, it is true: the possibilities of the almighty Garbage Collection feature of Objective-C 2.0 are tempting. It is a wondrous and many-splendored thing to be able to create objects willy-nilly and have them automagically disposed of when the time is right. But see, here’s the problem: Mac OS X doesn’t really know exactly when the time is right.

This isn’t a statement I make lightly; I’m a huge proponent of trusting your Mac to just “do the right thing,” because on a whole, it usually does. In this case, however, you need to take a bit of control back. You know your application far better than anyone else, including your computer, and as such you can do a far better job of managing your own memory.

Now let’s be realistic here: for the average small desktop application, whether you work in a memory-managed or garbage-collected environment doesn’t make a huge difference in memory footprint, and garbage collection does allow you to be somewhat lazier. As soon as your application becomes the tiniest bit complex, however, the benefits to managing your own memory are phenomenal, and you’ll find it’s actually easier than taking the extra work required to have an efficient garbage collected app.

Yes, it’s true: there actually is work involved in writing a good garbage collected app. Many people assume that garbage collection frees them of all memory management responsibility, but there are actually several organizational steps that must be taken to make sure all objects use strong references, among other more obscure gotcha’s. Trust me, managing your own memory is much more fun, and gives you quite a power trip!

I'm a little harder on Garbage Collection here than I probably should be. It's a great technique, is already very good at doing its job, and does have a handful of advantages over manual memory management.

It will always be true that if you want to squeeze every ounce of performance out of your application, manual memory management will be the only way to go. However, this adds the vector of memory management errors, which can be even more detrimental than the overhead incurred by garbage collection, especially in smaller applications.

Choosing an effective memory management scheme is something that always needs to be done on a per-application basis. Just because I prefer manual memory management doesn't mean that you have to, and I encourage you to learn more about Garbage Collection in Objective-C 2.0, you may find you prefer it. Just remember that garbage collection is not available on the iPhone as of now.

Just for the record, this is the most complete tutorial on memory management in Cocoa that I’ve ever written, and it’s very thorough if I do say so myself. As a result, it’s very long, and some of this information you don’t need to know when you’re just starting out. You should read this entire document if you want a solid understanding of Cocoa memory management (perhaps excluding “Making Your Own Objects Copyable” and “Advanced Autoreleasing”), but if you’re in a rush, the sections entitled “Your Responsibilities”, “Ownership in a Nutshell”, and “Allocation Patterns” should be sufficient as long as you also skim the two sections on increasing and decreasing the retain count (“Increasing the Retain Count: alloc, copy, and retain” and “Decreasing the Retain Count: release and autorelease”).

Enabling a Managed-Memory Environment in Xcode

In order to configure your project to use a managed-memory environment (vs. a garbage-collected environment), you need to… well, do nothing. By default, garbage collection is disabled, even for Leopard-only apps. That should be a big enough sign that using garbage collection isn’t all it’s cracked up to be.

Note: this may change as Objective-C’s garbage collection mechanism matures. Should this ever happen, you can access the garbage collection setting in Xcode from the “Build” tab of the Target Info pane, accessible from the “Edit Active Target” menu item in the Project menu. Just filter by “garbage” and you’ll see the garbage collection setting.

It has been pointed out to me by PyObjC's Bill Bumgarner that this isn't really the reason that garbage collection is disabled by default. Many existing frameworks and libraries cannot work with garbage collection enabled, and the choice to leave GC disabled by default reflects this far more than it reflects the maturity of the garbage collector.

Beyond that, memory management is what old-hand Cocoa developers are already used to. Having that suddenly ripped out from under them without them knowing would not be wise. Imagine the feeling of betrayal to one day discover that all of your lovely release statements in your latest app actually do nothing! The horror! (Memory management methods continue to exist in a GC environment, but are "no-ops," meaning calling them has no effect).

The Ownership Policy: Memory Management Made Simple

The problem many people new to managing their own memory face is simple: they over-think it. For whatever reason, the concept of memory management is frightening to many people coming from a development background in which they’ve never had to do it.

Repeat after me: there are no exceptions to the ownership policy. None, zero, zilch, nada. If you ever stop to wonder if a particular Apple class does something a certain way, or if you should do something a certain way, remember that fact. No exceptions, ever. I mean it.

Now that that’s out of the way, memory management becomes simple. Remember the basic rules of the ownership policy, and you won’t have any problems. Over-think it, and you will. Do not doubt this simple scheme, it works, I promise.

Your Responsibilities

When managing memory in a Cocoa application, your responsibilities really boil down to five simple things:

The first bullet point is worded a little confusingly. It will be changed in the final edition to: "Every time you create an object with alloc, you must release it, either when you are done using it with release, or when you are ready to hand it off to someone else with autorelease."

The last bullet point is also a little confusing, and will be changed to: "Never expect another object to release an object you own. If you need to wash your hands of an object before returning it, autorelease it."

This really all boils down to one simple rule that never fails:

All of that will make much more sense after reading the following sections. Just remember: don’t over-think this!

Ownership in a Nutshell

Memory management in Cocoa is driven by something known as retain counting. Unlike in garbage-collected environments which use reference counting, where an object is deallocated when there are no longer any variables or other objects pointing to it, in retain-counted environments each object has a special property known as its retain count, which is initialized to 1 when the object is created. If the retain count ever hits 0, the object is deallocated.

There are standard methods of every object that increase the retain count; likewise, there are methods that decrease the retain count. The way the ownership policy works, if you increment an object’s retain count by an amount, you acquire ownership of the object, and are therefore responsible for decrementing the retain count of the object by the same amount. You must finish your use of an object with a net retain count change of 0 (that is, increases must balance decreases), and you must not decrease the retain count of an object for which you did not increase the retain count. If you don’t own it (and don’t want to own it), don’t change it.

Why You Should Own Objects

Why would you want to own an object? Simple: if you want to keep it around for some length of time. Any time you want to store an object to an instance variable, for instance, you must take ownership of that object for the duration you want to access it. Once you no longer need it, you must then relinquish ownership (by decreasing the retain count), and set your instance variable to nil (so you don’t accidentally try to refer to an object that could no longer exist, if no one else owns it).

This would be a good place for a diagram. :)

The simple corollary to this occurs within a method. For whatever reason, there are some cases in which you must take temporary ownership of an object (it may be more efficient in terms of memory management, or there may not be a way to create an object without taking ownership — you’ll see what I mean). If you don’t want to keep a reference to this object in an instance variable, you must relinquish ownership before the method completes. If you don’t, that object will never be deallocated, and that’s how memory leaks occur (when you take ownership of objects that you never give up and aren’t using).

For the curious, there is no official, mandated, programmatic concept of “ownership” within Objective-C. In other words, there are no technological restrictions on how you increase and decrease the retain counts of your objects. Rather, the ownership policy is a technique for ensuring that all memory is managed properly. It was introduced in the Core Foundation framework many, many years ago, and it’s foolproof. Don’t try anything else, really.

A little fuzzy? Don’t worry, it all starts to make more sense when we start applying it to the real world.

Increasing the Retain Count: alloc, copy, and retain

There are four methods of NSObject (and therefore, every object in Objective-C) — and only four — that increase the retain count of an object: +alloc, -copy, -mutableCopy, and -retain.

Technically speaking, actually, retain — and release and autorelease below — are not actually part of the NSObject class, but rather the NSObject protocol, which is adopted by NSObject. You’ll learn more about protocols in chapter 9. There is no real functional difference, just a semantic one, so for all intents and purposes, you can consider them as being methods of NSObject.

The first method, alloc, is a class method that is called when you instantiate an object yourself. Thus, whenever you create a new object using alloc, it starts off with a retain count of 1. This is important, because alloc is not the only way to create objects; note the difference in these two ways to create a dictionary:

NSDictionary *dictOne = [[NSDictionary alloc] init];
NSDictionary *dictTwo = [NSDictionary dictionary];

Both lines in the above example result in an empty dictionary being created — they’re identical, with one exception: their retain counts. On the first line, the object is instantiated manually, with a resultant retain count of 1. On the second line, I use the NSDictionary convenience method +dictionary, and while I still get an empty dictionary out of it, it instead has a retain count of… 0?

“That can’t be right!” you cry in dismay. “You said that when objects’ retain counts reach 0, they’re deallocated.” Quite true, but in reality, the object referred to by dictTwo is actually autoreleased, which I explain below. The important thing to know here is that you’ve established ownership for dictOne but not dictTwo. That means you have a responsibility to either store (in an instance variable) or get rid of dictOne, whereas you must first take ownership of dictTwo if you want to keep it, and you must not attempt to get rid of dictTwo if you don’t, because you never owned it to begin with. It will otherwise go away by itself later (that’s the magic of autoreleasing).

So what if you’re handed an object you do want to keep, as an argument to a method call for instance, or perhaps you do want to hold on to dictTwo? That’s where retain and copy come into play! If you simply want to establish ownership of an object, ensuring that it won’t be discarded unless you say it’s OK, send the object a retain message. If you want to freeze the current state of the object and create your own, personal copy of it, use the copy (or mutableCopy) method.

copy and mutableCopy: The Real Story

There are actually two NSObject methods for copying objects: copy and mutableCopy. Most objects that are copyable only employ the copy method, while some objects — those that have mutable counterparts — implement both copy and mutableCopy. You can see which objects implement them by checking their documentation and looking at the protocols to which they conform: objects conforming to NSMutableCopying and NSCopying implement both, whereas those conforming to just NSCopying only implement copy. Objects implementing neither aren’t copyable — read “Choosing Between retain and copy” later in the chapter for an explanation.

In cases where both copy and mutableCopy are implemented, copy always returns the immutable variant of an object, and mutableCopy always returns the mutable variant. That is to say, calling copy on either an NSArray or an NSMutableArray always results in a new NSArray, while mutableCopy on either always results in an NSMutableArray, regardless of the original class of the object being copied. This can be very useful when you’re given an object of one variant but want one of the other. Just don’t forget that you do own this new object, and must therefore discard it when you’re finished.

Decreasing the Retain Count: release and autorelease

There are two methods of NSObject (actually the NSObject protocol) that decrease the retain count of an object: -release and -autorelease.

The release method immediately decrements the retain count of an object by one. You use this method when you are ready to relinquish your ownership of an object. Using the example above:

NSDictionary *dictOne = [[NSDictionary alloc] init];

/* ... do a bunch of stuff with dictOne ... */

[dictOne release];

After running the above, dictOne is deallocated, since the retain count hits 0. Let’s take a look at another common scenario:

@interface MyObject : NSObject {
    NSMutableArray *myArray;
}
@end

@implementation MyObject
- (id)init
{
    if (self = [super init])
    {
        myArray = [[NSMutableArray alloc] init];
    }
    return self;
}
- (void)dealloc
{
    [myArray release];
    [super dealloc];
}
@end

This common pattern above creates myArray when the object is allocated and initialized, then releases it when the object itself is released. This ensures that myArray is owned by the object for the object’s lifespan, but no longer than that. For more information on this common pattern, read “Allocation Patterns” later in the chapter. Note that the -dealloc method should NEVER be called directly — it is called for you by Cocoa when your object’s retain count hits 0, you only need implement it to do clean-up before your object is disposed of.

release is the easy one to understand, but it’s far less useful than its brother autorelease. autorelease, for whatever reason, always trips people up because it takes a significant amount of control away from them and gives it over to Cocoa. So, in order to make this as painless as possible, I will explain this in two ways: the “how you should use it” way, for the people content with accepting things as they are explained, and the “how it really works” way, for the people who don’t understand unless they also know the “why” (like myself — I didn’t fully get autorelease until I learned the latter explanation).

How You Should Use autorelease

According to the Apple documentation:

The autorelease method, defined by NSObject, marks the receiver for later release. By autoreleasing an object — that is, by sending it an autorelease message — you declare that you don’t want to own the object beyond the scope in which you sent autorelease.

Delayed Release”, “Object Ownership and Disposal”, Memory Management Programming Guide for Cocoa, ADC Reference Library

Thinking of autorelease in this context tells you exactly how you should use it. When you autorelease an object, it schedules its retain count to be decremented “some time in the future.” This is incredibly useful, particularly when you need to return a new object as the result of a method. If you create an object within the method (using alloc or some other retain-count-increasing method), you cannot release it before returning, because then you will be returning a non-existent object; you cannot simply return it without doing something, though, because you will leak memory (you have acquired ownership for an object without relinquishing it). In this case, you would autorelease the object: you effectively give up your ownership, but you ensure the object stays around long enough for the calling method to do something with it.

An example of autorelease in action is the concept of the convenience constructor used by many of Apple’s classes (often it is good practice to create your own following the same stylistic pattern). For instance, the +dictionary method of NSDictionary might look something like this:

+ (id)dictionary
{
    return [[[self alloc] init] autorelease];
}

First, the method creates a new dictionary (an instance of self, which here is NSDictionary) with alloc (ownership acquired, retain count is 1) and initializes it the normal way. Before returning, however, the method autoreleases it, effectively giving up its ownership and fulfilling its duties according to the ownership policy. The retain count stays at 1 long enough for the calling method to use it, then drops to 0 when it is no longer being used.

Bear in mind that all methods that do not explicitly contain alloc or copy (or retain, obviously) in their names return objects with a final potential net retain count of 0 — you never have to release anything you don’t explicitly create or retain. Likewise, if you want to keep an object (stored as an instance variable, for instance) you must retain it, since eventually that object’s retain count will fall to 0. This applies to all objects created with convenience methods like the one above.

A word about autoreleased objects: whenever, within a method, you want to use an object and never have to worry about it again, you should use convenience methods whenever possible — they return autoreleased objects, thereby freeing you of the burden of managing their memory. Since you never take ownership of them, you never have to give it up, and you can trust Cocoa to “do the right thing.” For objects that don’t have these convenience constructors, you will have to create them the normal way, but be sure to release or autorelease them before the method completes!

There are some rare cases in which it may be advantageous to use the conventional method of creating and releasing objects rather than convenience constructors, particularly on the iPhone. For more information, read the next section, and also “Advanced Autoreleasing” later in the chapter.

How autorelease Really Works

Internally, the Cocoa autorelease mechanism is a thing of beauty.

In Cocoa memory management, there is the concept of something called an autorelease pool. An autorelease pool is an object that contains a list of objects which have had an autorelease message sent to them. One of these is created automatically every time the Cocoa run loop iterates, and released when the run loop completes its iteration.

The run loop is the loop inside a Cocoa application that keeps it running. Button presses, keystrokes, timers, callbacks, and everything else that happens in an application happens within a single iteration of the run loop. Look at an example:

Let’s say I’m using an application, and I click a button. That mouse click event is scheduled at the hardware device level. The next time the application starts its run loop, it checks to see if there are any waiting events, and processes them sequentially. Cocoa looks up the method attached to the action of that button, and executes it, waiting for it to return. Once it returns (this method may, in turn, call any number of other methods, all of which are waited for), the iteration of the run loop continues until all events and other housekeeping tasks have been exhausted. The iteration completes, and starts all over again.

This is essentially how it works, but the Cocoa conceptual docs state that the pools are local to the event loop, not the run loop. This may be the same thing, but I'm not entirely sure, I need to do some research before finalizing this chapter.

Hopefully that makes it clear how every time any one of your methods is called, it will be within a part of this run loop — there is no possible way for a method to be called in a Cocoa app such that it does not occur within this loop, because it is always called in one of these four scenarios:

The only way a method can be called without being a part of the main run loop is if it occurs on a separate thread, which is why each new thread created requires its own autorelease pool. Let’s not talk about threads right now — for all intents and purposes, there is one autorelease pool, managed by the main run loop.

Whenever a new autorelease pool is initialized with init, it becomes the primary pool: all objects that are autoreleased from that point onward are added into it. The simplified loop looks something like this:

  1. The main autorelease pool is created.
  2. Events are fired, meaning any of your methods that are going to be called are called now.
  3. The autorelease pool is emptied, meaning any autoreleased objects generated by the use of your methods are released now, safely after all of your methods have finished working.

Read that over a few times, then sit back and let it sink in, it’ll make sense, I promise. Congratulations, you understand autoreleasing.

It is important to note that using autorelease on an iPhone can be dangerous because memory limits on applications are severely capped. If you have a loop in your code that creates a ton of autoreleased objects, you may hit your application’s memory limit before the run loop completes and allows them to be disposed of. In situations like this, even on the Mac, it is better to manually create and dispose of (with alloc/init and release) any objects you would ordinarily use convenience constructors for within each iteration of your loop. This way, your application’s peak memory usage stays low, never going above the memory required for one iteration of your loop. For more information on alternate solutions to this problem, read “Advanced Autoreleasing” later in this chapter.

Choosing Between retain and copy

Now here’s where it gets sticky: choosing between retain and copy. There are several factors that come into this decision, and it shouldn’t be made willy-nilly. There are a couple of rules of thumb for this, which I state at the end of this discussion, but still be aware of what you’re doing: you’re a big developer, you can make your own decisions.

First of all, not every object implements the copy (or, more specifically, -copyWithZone:) method. In fact, most don’t (though the most common ones, NSArray, NSDictionary, NSString, etc., do). You can discover whether or not an object supports copying by checking its documentation to see if it conforms to the NSCopying protocol. Note that the copy method is defined in NSObject, and is thus inherited by all objects, so its existence in a class’s documentation is hit-or-miss, and says nothing as to whether or not the object can actually be copied. Only objects that list conformance to NSCopying can definitely be copied.

If the object you want to keep doesn’t implement copy, then the decision is taken away from you; go ahead and retain it. If it does implement copy, however, there’s a bit more thought involved.

Recall that there are two “types” of objects: mutable objects (objects that can change) and immutable objects (objects that can’t). These aren’t programmatic types, mind you, but rather characteristics. If an object’s properties can be changed through methods, whether by you or someone else, it’s a mutable object; if they can’t, it’s immutable. If an object is guaranteed to be immutable — that is, it itself is immutable and it has no mutable subclasses — then there’s almost never any sense to make a copy of the object, it can’t possibly change under your nose. In this case, you should almost always use retain.

The problem occurs with mutable objects, and immutable objects with mutable subclasses (like NSString, NSArray, NSDictionary, etc.). Remember that, while a superclass is not necessarily a subclass, and therefore cannot be used in its place, a subclass is always an instance of its superclass, and is a legal substitution — in a method where I need to be passed an NSArray, I may very well be passed an NSMutableArray instead.

Let’s take the following two scenarios.

objectA is talking with objectB. objectA maintains an NSMutableArray with a list of food offered by a restaurant. This list changes frequently as new specials are added and removed. objectB always wants access to this list. Thus, objectA and objectB are designed such that objectA only sends the message -setFoodList: once to objectB, using the NSMutableArray as a parameter. objectA will take care of updating its array of foods, and because of design decisions, since we know both objects always want to share data, we’ve set it up so that we don’t need to keep calling setFoodList:. In this case, objectB retains the array, since we want changes from objectA to be reflected in objectB: we want it to be one single object referred to twice.

objectA is talking with objectB. objectA maintains an NSMutableArray with a list of schedule objects that represent who’s working on a given day. objectA rearranges the array frequently over the course of the week, but objectB always needs to know the last valid schedule handed to it via -setSchedule:. In this case, objectB copys the array, since it doesn’t want the array to change without its knowledge — it requires its own static record.

Even without knowing the inner workings of objectA in the previous scenario, it should be clear why the design decisions were made: in the first, we’re using a shared object, and whether it changes in the process is irrelevant; in the second scenario, we need to guarantee the object won’t change prematurely.

Basic rules of thumb:

Allocation Patterns

There are two common allocation/deallocation patterns that you will want to use in your Cocoa applications: the init/dealloc pattern, and the accessor pattern.

The init/dealloc pattern goes a little something like this:

@interface MyObject : NSObject {
    NSMutableArray *myArray;
    NSDictionary *settings;
}
@end

@implementation MyObject
- (id)init
{
    if (self = [super init])
    {
        myArray = [[NSMutableArray alloc] init];
        settings = [[NSDictionary dictionaryWithObject:@"Stumpy" forKey:@"name"] retain];
    }
    return self;
}
- (void)dealloc
{
    [myArray release];
    [settings release];
    [super dealloc];
}
@end

Since the +alloc method is standardized for every class (note: you should never ever override it in a subclass), the first chance your class gets to perform any initialization on your newly created objects is the init method. This is where you’ll want to create any objects that are necessary for your object to function properly. Since these are only useful if they’re stored to instance variables, you must have asserted ownership of them so that they won’t accidentally be destroyed on you. In the example, I created two objects: the first one using alloc/init, which grants me ownership; and the second using a convenience constructor, which does not, so I had to assert ownership manually using retain. Now both of these objects will hang around. (The pattern used in the init method above is discussed in more detail in Chapter 2, “Hello, Objective-C!”).

When your object is finally destroyed, however, its -dealloc method is called automatically. This method is called as part of NSObject’s release method, and is only called when an object’s retain count reaches 0. You should NEVER call an object’s dealloc method yourself, except the mandatory call at the end of your implementation to the superclass’s implementation.

In this dealloc method, you need to do some clean-up. Here, you remove any objects that you know will be in existence in your instance variables to ensure that there are no memory leaks. Occasionally, however, it is possible that certain instance variables may no longer contain objects where once they did (for instance, if someone uses an accessor to set the variable to nil). In this case, there is no danger, because sending release to nil simply does nothing — however, often people prefer to be safe rather than sorry, and implement something like this for variables that may be unset:

- (void)dealloc
{
    if (myArray)
        [myArray release];
    [settings release];
    [super dealloc];
}

The above dealloc implementation only releases myArray if it is set, whereas settings is released regardless.

Recall that in Objective-C there is no direct access to instance variables, and instead, accessors are used. The standard accessor pattern looks something like this:

- (NSString *)myName
{
    return myName;
}
- (void)setMyName:(NSString *)aName
{
    if (myName != nil)
    {
        [myName release];
        myName = nil;
    }

    myName = [aName copy];
}

- (NSDictionary *)settings
{
    return settings;
}
- (void)setSettings:(NSDictionary *)newSettings
{
    if (settings != nil)
    {
        [settings release];
        settings = nil;
    }

    settings = [newSettings retain];
}

The pattern is simple and straightforward: for the getter, simply return the instance variable. For the setter, first check to see if there is an existing value for the variable; if so, release and then nil it. Then, store the new value for the variable, using either copy or retain, whichever is more appropriate (see “Choosing Between retain and copy” earlier in this chapter for more on this).

Your Responsibilities (Revisited)

Whoo! That was intense. Time to make things a little bit simpler. Your responsibilities really boil down to five simple things:

Same edits as made above apply here as well.

This really all boils down to one simple rule that never fails:

Sound familiar? I told you memory management in Cocoa was easy.

Making Your Own Objects Copyable

Note that this part of the tutorial refers only to implementing the NSCopying protocol. If your object has two variants, an immutable version and a mutable version, you may want to implement NSCopying and NSMutableCopying for both. The process is similar, but don’t forget to mirror Apple’s behavior with this as closely as possible. For more details, read the section “copy and mutableCopy: The Real Story” earlier in this chapter.

Being able to make copies of your own objects with the copy method is sometimes quite convenient. Implementing this functionality isn’t terribly difficult to do, but does require a bit of thought. Let’s take a look.

First, you should specify your class as conforming to the NSCopying protocol. Protocols are covered in more depth in chapter 9, but for now, just know that a protocol is a set of method signatures that a conforming class must implement. The NSCopying protocol only has one method that needs to be implemented: -copyWithZone:. We specify that our object (MyObject) conforms to the protocol by putting its name in brackets after the superclass on the interface block, like so:

@interface MyObject : NSObject<NSCopying> {
    NSArray *myVar;
    NSDictionary *myOtherVar;
}

- (id)copyWithZone:(NSZone *)zone;

- (void)doThis;
- (NSString *)getThat;

- (NSArray *)myVar;
- (void)setMyVar:(NSArray *)newMyVar;
- (NSDictionary *)myOtherVar;
- (void)setMyOtherVar:(NSDictionary *)newMyOtherVar;

@end

Note: you specify multiple protocols by separating them with a comma. For example, to implement mutable copying, you would use the code <NSCopying,NSMutableCopying>.

Note that I added the protocol method’s signature (taken from Apple’s documentation for the NSCopying protocol) to my class’s interface. This is technically unnecessary, but can sometimes be convenient when you’re looking over your class’s methods.

Implementing the copyWithZone: method is easy: you just need to a) create your object like you normally would with alloc/init, and then b) set all of the instance variables to their correct values. The only difference is that instead of alloc, we use +allocWithZone:, passing in the zone parameter we’re given from copyWithZone:. My implementation for the above class would look like this:

@implementation MyObject
/* ... init method, and others ... */

- (id)copyWithZone:(NSZone *)zone
{
    id result = [[[self class] allocWithZone:zone] init];

    [result setMyVar:[self myVar]];
    [result setMyOtherVar:[self myOtherVar]];

    return result;
}

/* ... other methods ... */
@end

And that’s really all there is to it! Any time someone calls the copy method of your object, a copy is automatically made using copyWithZone:. Here, I used the -class method of self to get a reference to the current class, and then used that to perform the allocation. Note that the result of this method is not autoreleased — copy needs to result in a retain count of 1, so we return our new object “unbalanced.” Also note that I used the type id instead of (MyObject *) — the reasoning behind this is twofold:

This raises one last point: the above implementation is fine for objects descending directly from NSObject, like ours does. However, let’s say we subclassed a different object. If our superclass implements the NSCopying protocol, we would want to replace this line:

id result = [[[self class] allocWithZone:zone] init];

… with this line:

id result = [self copyWithZone:zone];

This ensures that any instance variables from the superclass are copied automatically into the new object. We only need to create a new object from scratch (as with our NSObject descendant) if there is no pre-existing copyWithZone: method to start us off.

Advanced Autoreleasing

We discussed earlier that there are cases in which waiting for the autorelease pool to be emptied by the run loop can cause a great strain on your application’s peak memory usage. Consider the following vague loop:

for (NSUInteger i = 0; i < 50000; i++)
{
    MyObject *myObj = [MyObject object];

    // do something with myObj
}

Even though all of the instances of MyObject created from the convenience method are disposed of with the completion of the run loop, that’s still 50,000 instances of MyObject that get created, which often will push your application’s peak memory usage quite high.

When I use the term “peak memory usage,” I mean your application’s memory usage at its highest point. As soon as an application needs memory, that memory is apportioned to it from the operating system. That memory is not reclaimed by the operating system, however, for quite some time after it’s done being used. For this reason, completely separate from your application’s memory usage in general, it’s important to keep your peak memory usage low, thereby avoiding having a bunch of wasted memory allocated to your application.

One solution to the above problem is to eliminate use of the convenience method and instead allocate and release the object yourself:

for (NSUInteger i = 0; i < 50000; i++)
{
    MyObject *myObj = [[MyObject alloc] init];

    // do something with myObj

    [myObj release];
}

For many cases, this will be sufficient: your application’s peak memory usage during this loop will not exceed that of a single instance of MyObject. However, there are a handful of situations in which this is not possible or preferable. For instance:

In these instances, it is often wise to create and use your own autorelease pool. Recall that calling the init method on a new autorelease pool sets it as the active one. We can create our own autorelease pool to hold the objects within a given iteration of a loop thusly:

for (NSUInteger i = 0; i < 50000; i++)
{
    NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
    MyObject *myObj = [MyObject object];

    // do something with myObj

    [myPool release];
}

In a memory managed environment, release and -drain have the same effect. However, in a garbage collected environment, while release does nothing, drain acts as a hint to the collector that it should perform a sweep. Therefore, to ease the transition between memory managed and garbage collected code, Apple recommends instead using drain in all cases. Therefore, the preceding code should be replaced with this:

for (NSUInteger i = 0; i < 50000; i++)
{
    NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
    MyObject *myObj = [MyObject object];

    // do something with myObj

    [myPool drain];
}

This has the same effect as replacing the convenience method calls with alloc/init/release calls — the created object is destroyed after each iteration of the loop — but the benefit here is that you don’t need to change any existing code, the changes are automatically applied to any autoreleased objects within the bounds of the pool’s init and release methods (including those you don’t see), and you can safely use any objects for which convenience constructors are the only way to create them. Pretty cool, huh? Just remember that it will no longer be safe to use any objects autoreleased within the bounds of this pool outside the scope of this pool (but that should’ve been obvious, right?).

It is important to note that the above example is not an instance of a good use of a local autorelease pool. The overhead of the creation of a single object is almost always too insignificant to make a difference. If you feel the itch to use local autorelease pools in this situation, you'd probably be better off doing some performance testing first to see if it's actually merited. If you do opt to use an autorelease pool, conventional wisdom says to consider optimizing, and rather than using a new pool each iteration, use one for every 10 iterations, for example.

Recent testing seems to indicate, however, that local autorelease pools are very inexpensive, and such optimization is probably not necessary. Apple makes no reference to such optimization in their documentation, and despite the concept's prevalence, it would seem that it is relatively unwarranted. So if you create a lot of autoreleased objects in a loop, go ahead, use a pool in every iteration! Just make sure you actually should be using a local autorelease pool.

Basic Garbage Collection

To be fair, I should probably include a section on Garbage Collection, even if it's relatively sparse.

In Conclusion

Cocoa memory management has quite a few ins-and-outs to be wary of, but the most important thing you can do is not over-think what you’re trying to do. Follow the simple rules lined out in “Your Responsibilities” at the beginning of this chapter. Trust me, you’ll be fine, and far more impressive than those garbage collection wimps.