Mandy's Tech Blog

View My GitHub Profile

Follow me on Twitter

Come work with me!

Site feed

Objective-C Reference Counting From the Bottom Up

I've been interviewing a lot of Objective-C developers, lately. In my mind, one of the most fundamental parts of working with Objective-C is understanding how reference counting works, and I'm consistently surprised by how many people are unable to clearly describe it. In this post, I'll outline the absolute minimum that anyone working with Objective-C ought to know about reference counting. If you find yourself in an interview with me, and you can describe everything in this blog post, I'll know where you got your information you'll be doing better than most.

The Basics

Objective-C objects are reference counted. This means that each object comes with a little counter, and anyone can modify the value of this counter by calling retain (to increase the value) or release (to decrease the value). When the value of the counter reaches 0, the dealloc method is called on the object, and any memory associated with the object is reclaimed by the system.

A good developer will make use of this system by calling retain on objects whose pointers are being stored in other objects and calling release on those objects when the pointer no longer needs to be stored. This is, in fact, the reason that we call it "reference counting": the counter is intended to indicate how many objects (or, in some cases, global variables) are holding references to the object.

Deadly Embraces and Retain Cycles

When two objects each call retain on the other, we call it a "deadly embrace": if no other action is taken, the objects will never be deallocated. This is widely regarded as a "bad thing".

When this sort of pattern occurs with more than two objects (for example, A retains B, B retains C, and C retains A), it is called a "retain cycle", and these can be even nastier to deal with.

Deadly embraces and retain cycles are dealt with by either preventing one object in the cycle from retaining its "parent" or by having explicit cleanup logic that is executed when it is certain that the objects are no longer needed. Really elite developers sometimes create proxy objects to stand in for one of the objects, although a full discussion of this technique is beyond the scope of this post.

Autorelease

The autorelease method is perhaps the most widely misunderstood concept in Objective-C memory management, although in practice, it's actually relatively simple.

At the beginning of each iteration through your application's run loop, an NSAutoreleasePool is created. At the end of each iteration it is destroyed. You can also create and destroy NSAutoreleasePools anytime you wish to do so (for example, you generally need to manually create and destroy them in background threads; more on this, later).

When you call autorelease on an object, the object searches for the most recently created NSAutoreleasePool in its thread. Assuming that it finds a pool, it adds itself to the list of objects that need to be released. When the NSAutoreleasePool is destroyed, it calls release on each object that is in its list--if an object is listed multiple times, release is called multiple times. If the object fails to find an NSAutoreleasePool, a message is logged to the console and the object is leaked (this is why you need to create an NSAutoreleasePool when doing work on a background thread).

Ownership and Object Creation

While the notion of "ownership" is not baked into the semantics of Objective-C, one object can be thought to "own" another if it has called retain on the object or has created an instance of the object via the alloc or copy methods. An object that owns another is responsible for calling release on that object when it is no longer needed.

This brings us to an interesting question: what should be done for a method that creates an object but isn't called alloc or copy? In order for the object to be active, its retain count must be at least 1, but the calling method cannot be relied upon to call release.

The answer, of course, is to use the autorelease method. This allows the retain count to be a non-zero value when the method returns, and if nothing is done to keep the object alive, it will be reclaimed when the active NSAutoreleasePool is destroyed.

Property Getters and Setters

Care must be taken when writing a method that sets a property value. The naive implementation of such a method looks like this:

- (void)setProperty:(id)value
{
	[propertyField release];
	propertyField = [value retain];
}

This is bad: if value is the same as propertyField, and the retain count of this object is 1 when we enter the method, the first line of the method will cause the object to be deallocated, and the second line will cause bad things to happen (although bad stuff may not happen immediately, which is worse).

This problem can be worked around by retaining value before releasing propertyField, by ensuring that the values are not equal to each other before performing the operation, or even by autoreleasing propertyField instead of releasing it (although this adds overhead).

Another interesting problem in this space involves methods that get property values. Again, the naive implementation of such a method looks like this:

- (id)property
{
  return propertyField;
}

99 times out of a hundred, this will work fine. This of course means that when you run into that one time it doesn't work, it's going to be a huge pain to debug. Here's the case in which you can get into trouble:

id foo = [obj property];
[obj setProperty:bar];
[foo doSomething];

If the retain count of propertyField is 1 at the beginning of this block, then the second line will cause it to drop down to zero and be reclaimed. This means that the third line will be performed on an object that has been deallocated, which is, to say the least, less than ideal. The solution to this is to modify the property getter so that it calls retain and autorelease on propertyField before returning it. This has no net effect on the retain count of the object, but it does ensure that the object will stay alive at least until the active NSAutoreleasePool goes out of scope.

So Many Projects…

I have a lot of side projects at various phases of investigation right now. So many, in fact, that I'm quite sure that I won't get around to actually implementing even half of them. Because completed projects are more important to me than the right to say that I wrote the code, I'm going to list some of them here in the hopes that someone on the lazywebs will be inspired to do something with my vague description.

Add New Features to Clang. The Objective-C runtime (at least Apple's version of it) allows any string to be specified as the name of a class, but existing Objective-C compilers only allow letters, numbers, underscores and dollar signs in class names. I'd really love to introduce the ability to have class names with greater variety. Apple has already shown their willingness extend the Objective-C language; I'm just going one step further. Here are a few more ideas that I'd like to see in clang:

  • Namespaces: a @namespace keyword could be added to Objective-C. Classes defined within a namespace would have the namespace and a period or double colon prepended to its name. This would allow everyone to abandon the practice of prefixing class names with silly two or three letter abbreviations. Anyone adding namespaces would probably want to add a @using keyword, too.
  • Greater applicability of the @dynamic keyword: currently, @dynamic can only be applied to properties. However, all sorts of methods could conceivably be dynamically implemented, and expanding the scope of @dynamic would allow someone to disable the compiler warnings for any method whose implementation will be provided at runtime.
  • Integration with other languages/runtimes: specifically, it would be pretty cool to be able to access objects in the Mono runtime and call methods on them, etc. This is probably the most unlikely of my ideas in this space to be implemented…

Refactor Monodevelop. Monodevelop has great support for things like syntax highlighting and code completion, but most of these features are very tightly tied to Gtk#, and if you want to access such features using a different UI framework, you're out of luck. I'd like to see Monodevelop refactored so that there is a library of common, platform-agnostic code that could conceivably be used from a variety of applications. I know I'd use it.

Write an Objective-C interop library for .NET that uses IDynamicMetaObjectProvider. Objective-C is a very dynamic language, so this sort of implementation makes quite a bit of sense. Individual Objective-C objects would likely inherit directly from SafeHandle and implement IDynamicMetaObjectProvider; all/most method invocation would happen via DynamicMetaObject. Then, using C#'s new dynamic keyword, most of the functionality in Cocoa would be accessible without the need for an enormous wrapper library.

A Very…Amazon…Christmas

You know that we’re in the Internet age when four out of five Christmas shoppers in my family use Amazon for shipping:Amazon boxes under a Christmas tree

The family members who sent us stuff through Amazon didn’t opt for gift wrap (it’s a pretty expensive option), so we have to wait for Christmas day to even know who each package is for, let alone what it is or who ordered it. Perhaps we are seeing the formation of a new holiday tradition?

Killer Apps—You're Doing it Wrong

During my morning journey through all that is new and noteworthy today, I ran across this article about a "killer development tool" for Linux. The author of this article claims that the tool is "going to make ISVs and other programmers start to love developing for Linux."

I must admit that my interest was piqued. I've done at least a little bit of development on each of the major platforms (Windows, Mac, various flavors of Linux), and I must say that tool support had a big effect on the pleasantness of each experience. Furthermore, while I understand that many developers are quite comfortable with Linux, the lack of a "big neon sign" saying "start here" was a big turn off for me (contrast this with Visual Studio for Windows and XCode for MacOS, and you'll begin to understand what I mean).

So what is this amazing tool? As it turns out, it's a package that helps developers find incompatibilities between their applications and various distributions of Linux.

That's a "killer" application? It's going to make me "love" developing for Linux?

Pardon my incredulity, but applying a band-aid over a problem that doesn't even exist (for the most part) on other platforms does not a killer application make. Nor will it make a person "love" developing for that platform. At the very most, one might say that it "makes the platform less painful" or "eases the burden associated with developing for the platform."

But "love?"

Not so much. I think I'll stick with Windows and MacOS for now, thankyouverymuch.

So today's lesson: if your "killer application" just eases the pain associated with a problem that doesn't exist in other environments, you're doing it wrong.

NSDuctTape now supports (rudimentary) two-way communication

This is just a quick note to let you know that NSDuctTape now supports communication in both directions (i.e.-you can now instantiate and manipulate Objective C objects from .NET). The support is still rudimentary, and it's not especially robust, but it's there, and it works. Currently, the new code is only available through Subversion, but I'll probably clean it up a bit and post a new download this weekend.