返回参数对象时 ARC 的内存泄漏

Posted

技术标签:

【中文标题】返回参数对象时 ARC 的内存泄漏【英文标题】:Memory leak with ARC when returning a parameter object 【发布时间】:2012-05-26 15:35:24 【问题描述】:

使用 ARC(自动引用计数)时,我遇到了一个奇怪(在我看来)和简单的内存泄漏情况。我正在使用 ios 代码,但我认为它应该普遍适用于 Objective-C。

我有一个以下函数,它在检查参数对象的属性后返回作为参数分配的对象。

- (id) returnParam:(id) obj

    // Do whatever filtering needed. Return nil for invalid input.

    return (NSNumber *)obj;

如果我在循环中调用这个方法如下,我可以看到分配的内存一直在增加,直到循环在 Instruments 中结束。

for(int i = 0; i < 1000000; i++)
        
    id obj = [[NSNumber alloc] initWithInt:i];        
    id obj2 = [self returnParam:obj];
    NSLog(@"attempt %@", obj2);

但是,如果我将returnParam 函数的内容按如下方式放入循环中,则一切正常。内存脚印一直保持相同的大小。

for(int i = 0; i < 1000000; i++)
        
    id obj = [[NSNumber alloc] initWithInt:i];

    // Do whatever filtering needed. Break on invalid input.

    id obj2 = obj;

    NSLog(@"attempt %@", obj2);

我已经消除了过滤部分(所以本质上该函数只将对象传递回调用者),同样的情况仍然存在。

不明白为什么这个序列没有按预期减少保留计数,我在这里和那里尝试了__weak__unsafe_unretained 的所有可能组合,但没有一个有效。

有人可以解释为什么这个(返回参数对象)不起作用,并建议我解决这个问题吗?

附:顺便说一句,它在 Instruments 中没有被捕获为内存泄漏事件,但我认为的情况是明显的内存泄漏。

【问题讨论】:

【参考方案1】:

这不是泄漏。这与-autorelease 的工作方式有关。扩展 -returnParam: 的作用:

-(id) returnParam:(id) paramToReturn

   // make sure the variable is in the scope for the entirety of the function
   id returnValue = nil;
   @try 
   
       // when the parameter is passed in, ARC will automatically retain it.
       [paramToReturn retain];
       returnValue = [paramToReturn retain]; // strong assignment (a = b)
       return returnValue;
   
   @finally
   
       [paramToReturn release]; // release the value we retained when we passed in the parameter
       [returnValue autorelease]; // autorelease the return value so it is still valid after the function exits.
   

现在,让我们将其与您的其他循环进行比较:

for (int i = 0; i < 1000; i++)

    id obj = [[NSNumber numberWithInt:i] retain]; // retain, because 'obj' is strong by default.
    id obj2 = [obj retain]; // retain, because 'obj2' is strong by default

    NSLog(@"attempt %@", obj2);

    [obj release]; // release because 'obj' is strong
    [obj2 release]; // release because 'obj2' is strong.

因此,在弹出下一个 autorelasepool 之前,您的变量不会被清理,这通常发生在 iPhone 应用程序中的下一个 NSRunLoop 滴答声中,或者可能发生在控制台应用程序中 @autoreleasepool 的末尾。

【讨论】:

您在 Instruments 中尝试过上述代码吗?如果打开优化器时代码“正常工作”,我不会感到惊讶,只要方法和循环都是 ARC 的。如果它仍然在自动释放池中增加内存,我也不会感到惊讶...... @bbum 不是我的回答所说的吗? Thus, your variables aren't getting cleaned up until the next autorelasepool is popped... @Richard 这是有道理的。我不理解(或没有深入思考)ARC 将通过调用autorelease 返回对象。因此,当我有huge 循环时,这可能表明我不应该调用返回对象(不一定是参数对象)的函数。或者您知道任何解决方法可以使这项工作发挥作用吗? @barley 在循环内部创建自己的@autoreleasepool。在完成主要解析的情况下,这是一种相当常见的解决方案,并且可以显着提高性能和内存占用。 我的建议是,如果启用优化,这实际上可能像 OP 所写的那样“正常工作”,但如果没有启用,则会失败。 IE。当从 ARC 代码调用到 ARC 代码时,编译器会优化对保留/释放/自动释放的调用(实际上它会消除自动释放)。但这可能是特定于优化器的问题,当然,出于调试目的,自动释放池可能仍然是必需的。

以上是关于返回参数对象时 ARC 的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

内存泄漏和僵尸有啥区别?

避免实体框架查询返回的列表中的内存泄漏

返回一个 C++ std::string 对象是不是可以避免内存泄漏?

iOS 内存管理

block探究一

arc下内存泄漏的解决小技巧