As I was preparing to post the QuartzDemos project on GitHub I decided to modernize the project and covert it to use Automatic Reference Counting (ARC). Overall this was fairly easy to do despite some of the slightly confusing rules related to bridging between Objective-C objects and Core Foundation types. Everything was looking good on the Simulator, but decided I should give it a quick once over on the device. I expected everything to work fine so I launched my first demo and crash! Hmm… probably just a debugger anomaly. So I fired it up again and crash! The debugging begins…
I quickly found the cause of the crashes was due to the way I was converting between UIColor
and CGColor
types. There were many places where I was using UIColor
to produce a CGColorRef
, e.g.:
// Define Colors CGColorRef startColor = [UIColor colorWithRed:1.000 green:0.969 blue:0.165 alpha:1.000].CGColor; CGColorRef endColor = [UIColor colorWithRed:1.000 green:0.400 blue:0.165 alpha:1.000].CGColor; NSArray *colors = [NSArray arrayWithObjects:(__bridge id)startColor, (__bridge id)endColor, nil]; // Create Gradient CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
This code worked fine in a manual memory management case as the UIColor
instance gets added to the current autorelease pool which means you can count on it being around for the duration of the current run loop. However, that behavior is changed when using ARC. As UIColor
is not being assigned anywhere the compiler assumes it’s not needed and immediately disposes of it. I was originally planning to go into more detail about why this is happening but noticed a tweet this morning that Mark Dalrymple, from The Big Nerd Ranch, beat me to the punch. Since Mark already did the heavy lifting, I’ll refer you to his post for the details. Instead, I’ll cut to the chase and explain how to fix this scenario.
After realizing the cause of the problem implementing a solution was fairly straightforward. I converted the code above to the following:
// Define Colors UIColor *startColor = [UIColor colorWithRed:1.000 green:0.969 blue:0.165 alpha:1.000]; UIColor *endColor = [UIColor colorWithRed:1.000 green:0.400 blue:0.165 alpha:1.000]; NSArray *colors = [NSArray arrayWithObjects:(__bridge id)startColor.CGColor, (__bridge id) endColor.CGColor, nil]; CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
Instead of defining the start and end colors as CGColorRef
I define them as UIColor
references and defer getting their underlying Core Graphics equivalent until it’s actually needed. This has the effect of extending the lifetime of the UIColor
instances long enough to resolve the problem I was running into.
For more examples of this solution in action I’ll refer you to the QuartzDemos project on GitHub.