转换将自动释放的CGColor返回到ARC的方法

Dru*_*erB 11 xcode cocoa objective-c core-foundation automatic-ref-counting

我正在将我的项目转换为使用ARC.我在NSColor上有一个类别,它有一个返回自动释放的CGColor表示的方法:

@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
Run Code Online (Sandbox Code Playgroud)

使用ARC执行此操作的正确方法是什么?我不想返回保留的CGColor.

XCode中的ARC转换器建议使用

return (CGColorRef)[(__bridge id)theColor autorelease];
Run Code Online (Sandbox Code Playgroud)

但这会导致以下错误消息:

[rewriter]将'autorelease'消息的结果转换为'CGColorRef'是不安全的; __bridge强制转换可能会导致指向被破坏对象的指针,并且__bridge_retained可能会泄漏对象

tc.*_*tc. 8

基本上是因为没有好的方法来转换ARC中的以下代码:

CGColorRef a = ...;
id b = [(id)a autorelease];
CGColorRef c = (CGColorRef)b;
// do stuff with c
Run Code Online (Sandbox Code Playgroud)

转换器移除-autorelease并添加一些桥接转换,但它被卡住:

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!
Run Code Online (Sandbox Code Playgroud)

但是迁移者应该选择做__bridge_SOMETHING什么呢?

  • 如果它选择__bridge,则b不再使用,因此编译器可以立即释放它.这崩溃了.
  • 如果它选择__bridge_retained,则所有权转移 "CF-land",但原始代码假定该对象将由自动释放池拥有.代码现在泄密了.

问题是ARC禁止调用,-autorelease但是没有文档化的方法来保证将对象添加到自动释放池中 - 这是从方法返回自动释放的CF类型的唯一好理由,但是很多 UIKit类都有CF -typed属性(并且MKOverlayPathView具有必须返回自动释放值的原子 CGPathRef属性).

这是我真正希望更好地记录的ARC的一个棘手问题.

你可以跳过一些可能会有不同程度成功的箍.为了增加ickiness:

  1. CFAutorelease()在没有ARC编译的文件中定义函数(添加-fno-objc-arc到目标设置中的编译器标志→构建阶段→编译源).我将此作为练习留给读者.这是有效的,因为ARC代码需要与MRC代码互操作.这可能是最干净的解决方案.(这必然会吸引评论说它不应该使用CF前缀,但只要你没有看到链接错误,C符号名称冲突通常是安全的,因为引入了"两级命名空间"在10.3左右.)

  2. 各种箍向它发送-autorelease消息或等同物.所有这些都有点乱,因为它们依赖于"愚弄"ARC,除了假设id与ABI兼容的最后一个void*.它们也可能比上面慢,因为它们需要查找一个类/选择器(objc_lookUpClass()并且sel_registerName()可能更快或甚至优化掉,但我不打赌它).

    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"));
    
    Run Code Online (Sandbox Code Playgroud)
  3. 通过分配__autoreleasing编译器无法优化的变量,强制将其添加到自动释放池中.我不知道这是保证(尤其是类似的东西objc_autoreleaseReturnValue(),并objc_retainAutoreleasedReturnValue()可能是可能的,但我认为这是不可能的,因为它会减慢的常见的情况(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];
    }
    
    Run Code Online (Sandbox Code Playgroud)

    (在重写相关方法之前,编译器/运行时也可以合作并使用静态调度/内联,但这看起来很棘手,而且没有自己的大量开销.)

  4. 使用typedef__attribute__((NSObject)).这是ARC规范中最容易混淆的部分,但是这样的东西似乎有效:

    typedef CGColorRef MyCGColorRef __attribute__((NSObject));
    -(MyCGColorRef)CGColor
    {
      ...
      return (__bridge MyCGColorRef)(__bridge_transfer id)theColor;  
    }
    
    Run Code Online (Sandbox Code Playgroud)

    认为你需要两个桥梁才能工作(一个将所有权转让给ARC而另一个转让给它); 如果你只是return theColor;我怀疑它被泄露了.从我对文档的阅读中,您应该只需要(__bridge_transfer MyCGColorRef)因为它从非ARC指针(CGColorRef)转换为ARC指针(MyCGColorRef),但这会让编译器抱怨.唉,文档没有给出如何使用__attribute__((NSObject))typedef的任何示例.

    请注意,您无需更改标题中的返回类型.这样做可能会启用自动释放的返回值优化,但我不确定编译器如何处理从MyCGColorRef到CGColorRef的转换.叹了口气.


Tho*_*iau 7

CGColor是Core Foundation对象.你不应该尝试使用autorelease它.相反,您应该重命名方法copyCGColor并返回保留的对象.

自动发布是Objective-C的概念.它在核心基金会层面不存在.

由于CGColor没有免费桥接到任何Objective-C类,尝试自动释放它是非常奇怪的(即使这可能有效).

几年后更新

现在CFFutorelease()处于CoreFoundation级别(自Mavericks和iOS 7以来可用).

  • 虽然它可能很奇怪,NSColor.h说`-CGColor`:"返回一个自动释放的CGColor." (5认同)