An ARC Transition Tale

If you are not a software developer, this post will probably of less interest to you. Otherwise, please read on…

Recently, I updated Wooly Tasks to use Automated Reference Counting (ARC). The transition from a non-ARC project to an ARC project is fairly straight forward, and mostly automated by Xcode 4. ARC allows the developer to worry less about Cocoa memory management rules, and instead focus on their object graphs and creating great software. ARC is a handy tool, but you now a have some new rules to learn (although, in truth they are probably less confusing as a whole than the old rules). One that is important is object lifespans. Essentially, this means the LLVM compiler can decide to release an object that it deems unused at any moment within the body of the code. We’ll see what kind of ramifications that presents soon.

It’s all fun and games until you shoot your eye out…

Once I had successfully transition (i.e. it compiled and linked), I tested Wooly Tasks using the simulator. To my delight, everything still worked as expected. OK, time to build and install on a device (in this case my iPhone 4S running iOS 5.1).



Crash. What the #^$#!!

Check with the Simulator again. Fine.

Back to the iPhone. Crash. Gah.

Unfortunately, it crashes due to an autorelease object, so there is no stack dump to figure out what the object is that is causing the issue (this is not entirely true, you can look at the crash log in Xcode, and this would give you plenty of clues…I didn’t think to look there first, unfortunately — lesson learned). All I get is a cryptic console log exception message stating:

“[Not A Type isEqual:] sent to deallocated object 0x….”

Well, huh.

Debugger spelunking time.

So, I started running it in the debugger and setting breakpoints starting from the first UIApplication delegate call and on down the line until I narrow it down to the setting up of the first view controller and displaying its view. Eventually I found the offending code (for brevity, I have left out the values, they were irrelevant to the issue, as is the particular color creation method):

CGColorRef color = [[UIColor colorWithHue:saturation:brightness:alpha:] CGColor];
self.layer.shadowColor = color;

Do you see the problem? Clang’s document on Automatic Reference Counting and lifetime semantics states:

By default, local variables of automatic storage duration do not have precise lifetime semantics. Such objects are simply strong references which hold values of retainable object pointer type, and these values are still fully subject to the optimizations on values under local control.

We have done the equivalent of:

UIColor *uiColor = [UIColor colorWithHue:saturation:brightness:alpha:];
CGColorRef cgColor = [uiColor CGColor];
self.layer.shadowColor = cgColor;

LLVM has every right to deallocate the UIColor object immediately after obtaining the CGColorRef from it, since by all accounts, it is no longer be used. This would then invalidate the data referenced by the cgColor local variable. When we set the shadowColor for the layer, we are potentially (in this case, we definitely were) stuffing an invalidated (but not nil’d) object value into it.

The Fix

The simplest solution was to rewrite the code like:

UIColor *color = [UIColor colorWithHue:saturation:brightness:alpha:];
self.layer.shadowColor = [color CGColor];
LLVM compiler will not be able to insert a release between accessing the CGColor and assigning it to the shadowColor.
And here you thought you could forget all those pesky memory rules.

Tags: , , , ,

Comments are closed.