【问题标题】:Convert method that returns an autoreleased CGColor to ARC将自动释放的 CGColor 返回到 ARC 的转换方法
【发布时间】:2012-07-23 02:18:10
【问题描述】:

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

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

[rewriter] 将结果转换为 'CGColorRef' 是不安全的 “自动释放”消息; __bridge 强制转换可能会导致指向 被破坏的对象和 __bridge_retained 可能会泄漏该对象

【问题讨论】:

  • 这里有一个很好的内存泄漏。 CGColorCreate 将创建一个 CGColor 对象,每次调用此方法时该对象都将保存在内存中。我强烈建议您这样做:CGColorRef colorRef = CGColorCreate(colorSpaceRGB, components); UIColor *retColor = [UIColor colorWithCGColor:colorRef]; CGColorRelease(colorRef); return retColor;
  • 你读过这个问题吗?我知道它在泄漏,这是我的问题。我想返回一个 CGColor,而不是 UIColor(无论如何,这是一个 OS X 问题,正如我提到的 NSColor 所示)。反正半年前就已经回答过了。

标签: objective-c xcode cocoa automatic-ref-counting core-foundation


【解决方案1】:

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

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

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

几年后更新

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

【讨论】:

  • 虽然很奇怪,但 NSColor.h 谈到 -CGColor:“返回一个自动释放的 CGColor。”
  • 在 OS X 10.9(和 iOS 7)中可用,现在有一个 CFAutorelease() 函数。
  • 你能举个例子吗?我在我的财产中添加了CF_RETURNS_RETAINED,但静态分析器仍然抱怨潜在的泄漏......
  • @IluTov CF_RETURNS_RETAINED 属性只告诉,这是retained,但你的内存泄漏是因为最近的@autoreleasepool-block(删除retain标记的对象)太远(或从未到达)。调用 autorelease 方法 (which ARC does not allow) 或将池块移动/添加到更靠近对象的位置。
【解决方案2】:

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

Docs

【讨论】:

    【解决方案3】:

    本质上是因为没有好办法在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,那么b 将不再使用,因此编译器可以立即 释放它。这会崩溃。
    • 如果它选择__bridge_retained,则所有权将回转移到“CF-land”,但原始代码假定该对象将归自动释放池所有。代码现在泄露了。

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

    这是 ARC 的棘手部分之一,我真的希望有更好的文档记录。

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

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

    2. 各种箍向它发送-autorelease 消息或等效。所有这些都有些混乱,因为它们依赖于“愚弄”ARC,除了最后一个假设id 与void* 兼容的ABI。它们也可能比上面的要慢,因为它们需要查找类/选择器(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 spec 中文档最容易混淆的部分,但类似这样的内容似乎有效:

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

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

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

    【讨论】:

      【解决方案4】:

      确实,在手动内存管理中,您可以将retain、release 和autorelease 任何CoreFoundation 对象,因为它们都是免费桥接 到至少NSObject。

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

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

      @interface NSColor (MyCategory)
      
      - (CGColorRef)CGColor __attribute__((cf_returns_retained));
      
      @end
      

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-12-18
          • 1970-01-01
          • 2011-12-12
          • 2011-06-23
          • 1970-01-01
          • 2011-12-14
          • 2013-07-10
          • 1970-01-01
          相关资源
          最近更新 更多