12

我正在将我的项目转换为使用 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

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

XCode 中的 ARC 转换器建议使用

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

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

[重写器] 将 'autorelease' 消息的结果转换为 'CGColorRef' 是不安全的;__bridge 强制转换可能会导致指向已销毁对象的指针,而 __bridge_retained 可能会泄漏对象

4

5 回答 5

8

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

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

转换器删除-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!

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

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

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

这是我真正希望得到更好记录的 ARC 的棘手部分之一。

您可以跳过一些障碍,这些障碍可能会取得不同程度的成功。按照增加粘性的顺序:

  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"));
    
  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];
    }
    

    (编译器/运行时也有可能合作并使用静态调度/内联,直到相关方法被覆盖,但这似乎很棘手,而且本身也有很大的开销。)

  4. typedef与 一起使用__attribute__((NSObject))。这是ARC 规范中文档最容易混淆的部分,但类似这样的东西似乎有效:

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

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

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

于 2012-08-14T19:51:33.907 回答
7

CGColor是一个核心基础对象。你不应该尝试使用autorelease它。相反,您应该重命名您的方法copyCGColor并返回一个保留对象。

自动发布是一个 Objective-C 的概念。它不存在于核心基础级别。

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

几年后更新

现在在 CoreFoundation 级别有 CFAutorelease()(从 Mavericks 和 iOS 7 开始可用)。

于 2012-07-24T17:51:43.333 回答
4

从 OS X 10.9 或 iOS 7 开始,您可以使用CFAutorelease()(在 CFBase.h 中声明)。

于 2013-11-01T15:53:47.060 回答
1

实际上,您可以在手动内存管理中retainrelease以及autorelease任何CoreFoundation对象,因为它们都是免费桥接至至少NSObject.

由于 ARC 禁止使用手动内存管理,我们应该以某种方式告诉编译器该做什么。一种方法是命名您的方法- (CGColorRef)copyCGColor;,以便编译器知道该方法返回具有 +1 保留计数的对象。

但是,如果您像我一样喜欢简单的“CGColor”用于此类方法,您可以附加__attribute__((cf_returns_retained))到方法定义中:

@interface NSColor (MyCategory)

- (CGColorRef)CGColor __attribute__((cf_returns_retained));

@end
于 2013-02-21T08:22:44.950 回答
0

我认为您想在这种情况下使用 __bridge_transfer 。

文档

于 2012-07-24T17:54:55.293 回答