ARC Gotcha - Unexpectedly Short Lifetimes

Mark Dalrymple's Headshot
Mark Dalrymple cocoa ios

Disassembly One of our engineers was working on a project and wrote some code that crashed when running on a device:

    CGColorRef color = 
        [UIColor colorWithRed: 0.2  
                        green: 0.3  
                         blue: 0.4  
                        alpha: 1.0].CGColor;
    [[self.view layer]
        setBackgroundColor: color];

It looks reasonable. Could it be a toolkit bug? It'd be weird for something to be so obviously broken in a fundamental CoreAnimation call. It's like the CGColor is pointing to garbage when it gets used. Almost as if the newly-created UIColor suddenly vanished and took the CGColor down with it.

What could be releasing objects automatically? ARC - that's its job. From ARC's point of view, the UIColor created with -colorWithRed:... was used to access the CGColor, and then is no longer needed. The UIColor wasn't assigned to anything, therefore ARC decided that the object could be cleaned up at the semicolon at the end of the expression. Therefore it's a candidate for disposal and got released. The disassembly, viewable in Xcode with Product->Generate Output->Generate Assembly file, shows that this is the case:

    // UIColor colorWithRed:...
    blx _objc_msgSend  
    ...
    // Hang on to the object, bypassing the autorelease pool
    bl  _objc_retainAutoreleasedReturnValue  
    ...
    // -CGColor
    blx _objc_msgSend
    ...
    // The UIColor goes away
    bl  _objc_release 

    // and then the layer calls happen

Ordinarily, you'd expect the UIColor to be in an autorelease pool, and so would survive until the end of the function. In this case, though, you can see ARC's "avoid the autorelease pool" optimization is being used: if a method returns an autoreleased object, and the caller doesn't otherwise need to hang on to it, ARC can avoid the trip to the autorelease pool. The method will return a retained object and objc_retainAutoreleasedReturnValue will dispose of it. That's why the UIColor is not kept alive by the autorelease pool.

So how to fix this? One idea we tried was assigning the new UIColor to a pointer variable, thereby taking it out of that very short-lived expression. After that, get the CGColor out of it:

    UIColor *uicolor = [UIColor colorWithRed: 0.2 
                                       green: 0.3 
                                        blue: 0.4 
                                       alpha: 1.0];
    CGColorRef color = uicolor.CGColor;
    [[self.view layer] setBackgroundColor: color];

This seems to work - no crashes. Ship it!

Unfortunately, it's a ticking bomb. The returned UIColor is being assigned to a strong-reference variable, but it doesn't get used in the method after extracting the CGColor. ARC is within its rights to release the object between fetching the CGColor and setting the layer's background color. ARC variables are released as soon as the optimizer decides that they are no longer referenced, so the compiler is free to release uicolor after fetching the CGColor. Current compiler implementations do seem to wait until the end of scope, but that's not guaranteed. The LLVM Project's ARC notes say: "By default, local variables of automatic storage duration do not have precise lifetime semantics."

This means the compiler can do whatever it wants. The currrent Apple LLVM 3.0 compiler releases uicolor at the end of the method. If the optimizer, either now or in the future, decides that uicolor's lifetime is over, it'll get released.

So, how to fix this? Three Four ways. You can annotate the local variable with the objc_precise_lifetime attribute if you want it to live til the end of the scope. Update: Thanks to TJ Usiyan for pointing out the `NSVALIDUNTILENDOFSCOPE` foundation wrapper for objcpreciselifetime_

You can explicitly retain the CGColor so it doesn't float away, and release it later:

    CGColorRef color = 
        CGColorRetain ([UIColor colorWithRed: 0.2 
                                       green: 0.3 
                                        blue: 0.4 
                                       alpha: 1.0].CGColor);
    [[self.view layer] setBackgroundColor: color];
    CGColorRelease (color);

The uicolor is welcome to disappear because the CGColor will stay around.

You can put in a call to self at the end of the method:

    [uicolor self];

And finally you can also bounce through the uicolor whenever you need its CGColor. This keeps the variable in use so it won't be released yet:


    UIColor *uicolor = [UIColor colorWithRed: 0.2 
                                       green: 0.3  
                                        blue: 0.4  
                                       alpha: 1.0];
    [[self.view layer] setBackgroundColor: uicolor.CGColor];

Of these, I like the last one better. Less code, less explicit management of object lifespan (which is why we have ARC in the first place), and also makes it very clear from where the CGColor was derived.

_(Thanks to Mike Ash for exploring some of these ARC behaviors with me. Thanks to Juri Pakaste and Dave Dribin for the [uicolor self] trick.)

_

Recent Comments

comments powered by Disqus