Modern Cocoa Memory Management

Objective-C in the Cocoa and Cocoa touch environment has always had one particular source of plight for newcomers in the realm of memory management. In the olden days, we Cocoa programmers had Reference Counting, a form of manual memory management, and though the rules are simple, they were also hard to master and easy to screw up. After a brief and half-hearted stint at using fully managed memory in the form of OS X’s Garbage Collector, Apple has now deprecated the technology (which they never could get running well enough on iOS).

These days, we have Automatic Reference Counting (ARC) on both platforms, which is somewhere in between. In this article, I will explain the fundamentals of Cocoa memory management, what you must know and what can be left to the tools.

Smells like Garbage Collection

At first glance, ARC seems an awful lot like Garbage Collection: it is automatic after all. But despite that specious assumption, ARC is in fact quite different from GC. ARC is a compile time technology, which means there is no collector running with your app’s process, and this saves on performance. But it also means ARC isn’t as capable as a full garbage collector.

The hint is in the name. If you look closely, it’s Automatic Reference Counting, not Automatic Memory Management.

In essence, this means ARC doesn’t relieve you the programmer from knowing the memory management rules, it only relieves you from writing memory management code. It doesn’t mean ARC is hard, it just means you have still have to pay a little bit of attention instead of letting the system do all the work (as it ought to do). ARC is a compromise.

Ownership

The key and fundamental principle of Cocoa’s memory management rules is and always has been about Ownership. Learn this principle and learn it well and ARC and Manual Reference Counting will make absolute and perfect sense.

Instead of a system process exploring the runtime’s object graph, Cocoa’s reference counting system relies on a compile time Ownership model to determine the lifetime of objects at runtime. It can be expressed in three simple axioms:

  1. An object will exist in memory so long (but no longer) as at least one object maintains ownership of it.
  2. To keep an object, you must take ownership of it. If you are done with an object, you must relinquish ownership of it.
  3. More than one object may share ownership of a given child object.

Ownership is acquired in one of the following ways:

  1. By allocating an object in memory by using any methods starting with -new, -alloc, or -copy.
  2. By requesting ownership of the object.

    In ARC, this is by assignment to a strong property or variable (object instance variables default to strong ownership under ARC, as they ought to).

    In MRC, this is by assignment to a retain property or by by sending the object the retain message (object instance variables don’t do any of this for you under Manual Memory Management, so if you wish to retain the object when setting it to an instance variable, be sure to send it retain).

If you do neither of these things, you don’t own the object and you should treat it as though it will go away after the scope in which it’s being used. That means you don’t have to do anything special to keep an object around for the duration of a method block, but unless you hang on to it explicitly, it will go away.

To relinquish ownership, all you have to do is remove the strong/retain reference to it (by nil-ing out the property) in ARC or MRC. Or by sending the object a release or autorelease message, in MRC only.

When and When Not

After mastering the concept of Ownership, the rest just falls naturally into place. To repeat from above, If you don’t claim ownership of an object, you can’t expect it to be around for any longer than its current scope. That’s the API contract you make with Cocoa’s memory management rules, whether ARC or MRC.

With that knowledge, you can thankfully let ARC take care of most of the rest (with one exception, as we’ll see later). Some examples of when you the programmer need to do work, and when you don’t:

- (void)someMethod {
    id localObject = [SomeClass new]; // creates ownership, but only for the method’s scope
    // ... do your stuff with localObject
    return; // ARC automatically relinquishes ownership of localObject for us, because our object didn’t take Ownership
}

- (void)setupState {
    // In the below case, we assign the object to a strong property, thus taking ownership.
    // Even though the -new method also comes with ownership, it’s local like the above example, so we get the intended behaviour of a single ownership. ARC figures it out for us.
    self.instanceProperty = [SomeClass new];
    // ... etc.
}

- (void)addToStateArray:(id)otherObject {
    [self.arrayProperty addObject:otherObject];
    // In this case, we don’t directly claim any ownership because our Array does that for us.
}

Where ARC is weak compared to GC

Garbage Collection provides the programmer with the contract that it will take complete control over managing memory in the application process, whereas ARC only makes the claim of relieving the programmer of writing ownership machinery. This means, unlike GC, ARC does not fully manage every aspect of process memory. Most importantly for Cocoa developers, this means ARC cannot break ownership cycles.

A cycle occurs when a parent object claims ownership of a child object who either likewise claims ownership to the parent or owns a descendent who claims ownership to the parent. It might look like the following, where -> means ownership:

A -> B -> C -> A

In such a scenario, keeping in mind the first and second axioms of Cocoa memory management, object A can never never be deallocated because there is an ownership cycle. C owns A, but can’t be deallocated because B owns C. And B can’t be deallocated because A owns B. And so on. To solve this, the programmer must take responsibility and use a weak reference.

A weak reference is just that: a reference to an object without claiming ownership of it. These also have the nice benefit of automatically being set to point to nil when the object at the other end disappears (I’m with Wolf Rentzsch on this one: if the runtime is capable of this, why didn’t they just go all the way and do real GC?). Here’s an example of solving an ownership cycle with weak, from the parent:

- (void)setChild:(Child *)child {
    self.child = child;

    // We assume the class Child has a property that looks like
    // @property (weak) id parent;
    self.child.parent = self;
}

If the Child class had a property that wasn’t denoted as weak, then it we would have an ownership cycle, but with weak, we can have a healthy object graph devoid of leaks or cycles.

ARC is a compromise

Cocoa memory management has always been a source of consternation for newcomers. Even though ARC aimed to solve that by taking more control over memory management, it’s not a full solution like Garbage Collection. In order to master it, you still must master the above concepts. But by internalizing the principles of Cocoa’s memory management, ARC takes care of the rest.

Join the Discussion 👂🤔✍️

Please read the Discussion Guidelines before replying.

☑️ Email me when someone replies.

Speed of Light