Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert method that returns an autoreleased CGColor to ARC

I'm in the process of converting my project to using ARC. I have a category on NSColor with a method that returns an autoreleased CGColor representation:

@implementation NSColor (MyCategory)

- (CGColorRef)CGColor
{
    NSColor *colorRGB = [self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
    CGFloat components[4];
    [colorRGB getRed:&components[0]
               green:&components[1]
                blue:&components[2]
               alpha:&components[3]];
    CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGColorRef theColor = CGColorCreate(space, components);
    CGColorSpaceRelease(space);
    return (CGColorRef)[(id)theColor autorelease];
}

@end

What is the correct way to do this with ARC? I don't want to return a retained CGColor.

The ARC converter in XCode suggest using

return (CGColorRef)[(__bridge id)theColor autorelease];

but that results in the following error message:

[rewriter] it is not safe to cast to 'CGColorRef' the result of 'autorelease' message; a __bridge cast may result in a pointer to a destroyed object and a __bridge_retained may leak the object

like image 694
DrummerB Avatar asked Jul 24 '12 17:07

DrummerB


3 Answers

Essentially it's because there is no good way to convert the following code in ARC:

CGColorRef a = ...;
id b = [(id)a autorelease];
CGColorRef c = (CGColorRef)b;
// do stuff with c

The converter removes -autorelease and adds some bridged casts, but it gets stuck:

CGColorRef a = ...;
id b = (__bridge_transfer id)a;
CGColorRef c = (__bridge_SOMETHING CGColorRef)b;
// do stuff with c. Except the compiler sees that b is no longer being used!

But what should the migrator choose to do for __bridge_SOMETHING?

  • If it picks __bridge, then b is no longer used so the compiler can immediately release it. This crashes.
  • If it picks __bridge_retained, then ownership is transferred back to "CF-land", but the original code assumed that the object would be owned by the autorelease pool. The code now leaks.

The problem is that ARC forbids calling -autorelease but does not have a documented method to guarantee that an object is added to the autorelease pool — the only good reason to do this to return an autoreleased CF type from a method, but plenty of UIKit classes have CF-typed properties (and MKOverlayPathView has an atomic CGPathRef property which must return an autoreleased value).

This is one of the tricky bits of ARC that I really wish was better documented.

There are a few hoops you can jump through which might work with varying degrees of success. In order of increasing ickiness:

  1. Define a CFAutorelease() function in a file compiled without ARC (add -fno-objc-arc to the compiler flags in target settings → Build Phases → Compile Sources). I leave this as an exercise to the reader. This works because ARC code needs to interoperate with MRC code. This is probably the cleanest solution. (This is bound to attract a comment saying that it shouldn't use the CF prefix, but as long as you don't see a link error, C symbol name collisions are generally safe because the of the "two-level namespace" introduced in 10.3 or so.)

  2. Various hoops to send it an -autorelease message or equivalent. All of these are a bit messy because they rely on "fooling" ARC, except the last one which assumes id is ABI-compatible with void*. They're also probably slower than the above because they need to look up a class/selector (objc_lookUpClass() and sel_registerName() might be faster or even optimized away, but I wouldn't bet on it).

    return (__bridge CGColorRef)[(__bridge id)theColor performSelector:NSSelectorFromString(@"autorelease")]
    
    [NSClassFromString(@"NSAutoreleasePool") addObject:(__bridge id)theColor]
    return theColor;
    
    return (__bridge CGColorRef)((id(*)(id,SEL))objc_msgSend)((__bridge id)theColor,NSSelectorFromString(@"autorelease"));
    
    return ((void*(*)(void*,SEL))objc_msgSend)(theColor,NSSelectorFromString(@"autorelease"));
    
  3. Force it to be added to the autorelease pool by assigning to an __autoreleasing variable that the compiler can't optimize away. I'm not sure if this is guaranteed (in particular, something similar to objc_autoreleaseReturnValue() and objc_retainAutoreleasedReturnValue() might be possible, but I think this is unlikely since it would slow down the common case of (NSError * __autoreleasing *)error).

    -(id)forceAutorelease:(id)o into:(id __autoreleasing*)p
    {
      *p = o;
      return p;
    }
    
    -(CGColorRef)CGColor
    {
      ...
      CGColorRef theColor = CGColorCreate(...);
      CGColorSpaceRelease(space);
      id __autoreleasing temp;
      return (__bridge CGColorRef)[self forceAutorelease:(__bridge_transfer id)theColor into:&temp];
    }
    

    (It also might be possible for the compiler/runtime to co-operate and use static dispatch/inlining until the relevant methods are overridden, but that seems tricky and not without significant overheads of its own.)

  4. Use a typedef with __attribute__((NSObject)). This is the most confusingly documented parts of the ARC spec, but something like this seems to work:

    typedef CGColorRef MyCGColorRef __attribute__((NSObject));
    -(MyCGColorRef)CGColor
    {
      ...
      return (__bridge MyCGColorRef)(__bridge_transfer id)theColor;  
    }
    

    I think you need two bridges for this to work (one to transfer ownership to ARC and another to); if you simply return theColor; I suspect it is leaked. From my reading of the docs, you ought to just need (__bridge_transfer MyCGColorRef) because it's converting from a non-ARC pointer (CGColorRef) to an ARC pointer (MyCGColorRef), but that makes the compiler complain. Alas, the docs do not give any examples of how to use __attribute__((NSObject)) typedefs.

    Note that you do not need to change the return type in the header. Doing so may enable autoreleased return value optimization, but I'm not sure how the compiler handles the conversion from MyCGColorRef to CGColorRef. Le sigh.

like image 75
tc. Avatar answered Oct 23 '22 02:10

tc.


CGColor is a Core Foundation object. You should not attempt to use autorelease with it. Instead, you should rename your method copyCGColor and return a retained object.

Auto-releasing is an Objective-C concept. It does not exist at the Core Foundation level.

Since CGColor is not toll-free bridged to any Objective-C class, it is very weird to try to autorelease it (even if that might work).

Update a few years later

There is now CFAutorelease() at the CoreFoundation level (available since Mavericks and iOS 7).

like image 43
Thomas Deniau Avatar answered Oct 23 '22 02:10

Thomas Deniau


Starting with OS X 10.9 or iOS 7 you can just use CFAutorelease() (declared in CFBase.h).

like image 30
fjoachim Avatar answered Oct 23 '22 02:10

fjoachim