【问题标题】:Why is ARC using autorelease at all?为什么 ARC 完全使用自动释放?
【发布时间】:2012-06-25 14:39:34
【问题描述】:

我在 ARC 下使用 no 自动释放池运行此代码:

- (NSString*) outName {
    if (!outName) {
        outName = [[NSString alloc] initWithFormat:@"whatever"]; // or stringWithFormat
    }
    return outName;
}

调试器说它每次都在泄漏 single outName 实例,而没有适当的池。

如果我将代码更改为,则不会发生这种情况

- (NSString*) outName {
    if (!outName) {
        outName = @"whatever";
    }
    return outName;
}

这是我做不到的(这个例子显然被简化了)。此外,如果我在调用代码中创建一个自动释放池(我想避免),泄漏消息就会消失。

为什么 ARC 坚持自动释放这个对象,它保存在 strong 属性中?更重要的是,我怎样才能避免这个警告?

【问题讨论】:

  • 你为什么会认为它是要求一个自动释放池?
  • ARC 不会删除retainrelease,它只是消除了您使用retainrelease 的需要。如果没有自动释放池,项目就会泄漏。 (将其移至评论,因为我不确定 100%,如果人们同意评论,我将取消删除我的答案)
  • 如果这基本上是一个 getter,为什么不简单地返回 outName 让 setter 担心内存管理?
  • @CodaFi 首先:我得到“__NSCFString autoreleased,没有池子 - 只是泄漏”......我当然可以忽略这个,因为它本质上是一个只读实例,但如果持有对象释放,对象确实会被泄露。
  • @JamesWebster 这个答案更适合作为评论,谢谢。我的问题是,为什么 ARC 选择一种自动释放策略,在这种策略中,人类只需返回牢固持有的 ivar 而无需执行 retain] autorelease]。我想答案是“安全”。显然,这样做的方法是objc_retainAutoreleaseReturnValue

标签: ios automatic-ref-counting


【解决方案1】:

这是一个所有权问题。

让我们谈谈您首先分配给自己的 NSString。 当你分配一个对象时,堆中的内存是为那个对象保留的(除非你 allocWithZone: 到另一个位置)。保留计数隐式为 1,并且您拥有该对象,即您有责任在完成后释放它。如果您要返回指向该对象的指针,即返回该对象,您并没有完全放弃确保该对象不泄漏的责任。您不能释放它,因为保留计数将变为 0,并且该对象将被释放。你自动释放它,确保在你的运行循环结束时(或更早),对象将被释放并可能被释放。如果返回的对象需要存活更长时间,调用函数负责保留返回的对象。

如果没有自动释放池,您将泄漏,因为指定的 autoReleasePool 为 null(请记住它可以发送 null 消息,这就是为什么这不会只是崩溃而不是泄漏)。

带有固定@"whatever" 的示例不会泄漏,因为编译器会为该字符串保留程序内存,而-release 对它们没有影响。对于一些低价值的 NSNumbers 也是如此。

正如 James 所说,ARC 不会删除保留释放和自动释放概念。

编辑:outName 如何声明为 ivar/property?

【讨论】:

  • outName 最初声明为 readonly__strong iVar,但现在它是 strong 属性。此外,我将 setter 完全放在了其他地方,这(正如您猜到的)并没有改变任何东西。
  • +1 很好的解释。那么,除了创建自动释放池之外,现在还有什么方法可以避免这种情况?
  • 您使用的是@synthesize 还是@dynamic?
  • NVM,ARC会在任意对象返回前插入函数objc_retainAutoreleaseReturnValue(outName)。
  • 打开编译器优化后,运行时可能会查找堆栈并删除调用代码中的保留调用 (objc_retain()) 和方法中的 objc_retainAutoreleaseReturnValue() 调用,然后再返回,这样它就不会不要在自动释放上浪费时间。最重要的是,您需要有一个 NSAutoreleasePool 来进行正确的 Objective-C 内存管理。许多库存的 Apple 代码依赖于自动发布。如果没有它,内存管理会更加混乱。
【解决方案2】:

Jared's 的回答很好,但 ARC 的部分工作方式是使用命名约定。 outName 的方法名意味着它返回一个 autoreleased 值,所以如果有一个 de-ARC-ifier,你的最后一行看起来像这样:

return [[outName retain] autorelease];

显然这需要一个自动释放池。

这在您的第二个示例中不会发生,因为您返回的是一个常量,因此保留/自动释放被优化掉了。

【讨论】:

  • 抱歉,我不明白你为什么说我没有使用strong 属性。 self.outName = outName = 在 ARC 下是一样的。两者都会将对象的保留计数增加 1,因为 ivar ITSELF 是 __strong
  • 因为他没有使用他的属性的getter和setter,实际上他正在覆盖getter。
  • @JaredKipe 你说的第二点是对的;我将删除该引用。
  • @Yar 抱歉,没有注意到您正在覆盖获取。尽管如此,关于命名约定的观点仍然存在。我已经更新了我的答案以使其更清楚。
  • 谢谢,我已经通过将 ivar 的使用推送到可以实际访问它的类来制定解决方案。但是,我将检查命名约定点,看看我是否可以通过这种方式获得任何牵引力。
【解决方案3】:

当 ARC 将 ivar(或任何对象)返回给调用方法时,它必须保证在当前 RunLoop 完成之前它不会被释放。作为程序员,您知道它永远不会发布,但 ARC 依赖于算法保证(最佳实践)。 ARC 将调用retain] autorelease]除非变量指向一个常量(没有用NARC 实例化),因此不存在被释放的风险。

您应该避免此警告。你需要修复你的代码。您可以添加自动释放池。如果您不想这样做,另一种方法是将使用 ivar 的逻辑下推到实际持有 ivar 的对象。

【讨论】:

    【解决方案4】:

    奇怪,我有一个非常相似的方法,我使用它并没有泄漏。它看起来像这样:

    -(NSString *) dataFilePath {
    
        NSString *appendPath;
        if (isPowerMode==YES) {
            appendPath = kDataFileNamePower;
        } else {
            appendPath = kDataFileNameClassic;
        }
    
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        return [documentsDirectory stringByAppendingPathComponent:appendPath];
    }       
    

    所以也许从那里开始,您可能想在方法的开头声明一个空的 NSString 指针,然后填充并返回它而不是 outName。

    【讨论】:

    • 相似的不一样。他正在泄漏,因为他检查了一个 nil var,如果是,则分配一个新的。他做了一个吸气剂,你完全做了一个新方法。
    • 您在没有自动释放池的情况下运行此方法?你确定吗?这种方法肯定会泄漏。
    • 我按原样运行它,它不会泄漏(ARC 开启)
    • @KaanDedeoglu 这不是问题。你的主运行循环总是已经有一个自动释放池。
    • 为了澄清我的答案,我不在代码中的任何地方使用@autoreleasepool
    猜你喜欢
    • 1970-01-01
    • 2013-07-10
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-07
    • 2011-12-18
    • 2013-12-23
    相关资源
    最近更新 更多