ARC Gotcha – Unexpectedly Short Lifetimes
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
NS_VALID_UNTIL_END_OF_SCOPE foundation wrapper for objc_precise_lifetime
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);
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:
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.)