【问题标题】:A Phantom Memory leak with ARC in XCode 4.5 where dealloc is definitely called or an Instruments issue?XCode 4.5 中带有 ARC 的 Phantom Memory 泄漏,其中肯定会调用 dealloc 或 Instruments 问题?
【发布时间】:2012-12-25 01:46:31
【问题描述】:

序言;这不是一个普遍的“我有一个有泄漏的巨型应用程序”的问题。这是关于自动引用计数在具有完整源代码的几乎微不足道的演示应用程序中无法正常工作的特定问题,或者是微妙的代码生成或编译器问题,或者是 Instruments 中的错误。 (TLDR:哦。实际上是一个奇怪的小竞争条件)

我对 Instruments 的“分配”列表显示实例泄漏这一事实感到困惑,然而,我有该类的一个实例,只有一个,而 ARC 导致调用 dealloc 方法,我知道它是被调用是因为在 dealloc 完成时会打印一条 NSLog 消息,但它仍然显示在 Instruments 的泄漏列表中。

retainCount 永远不会超过 1。它没有被任何人保留,它正在被释放,但它看起来像是一个“泄漏”,因为它在 Instrument 的泄漏中显示为一个活动实例。

这怎么可能?

我仍在使用 ARC 学习 Objective-C,所以我想我一定犯了一个常见的初学者错误。这是我唯一的 init 和我的对象的 dealloc:

- (id) initWithMessage:(NSString*)messageForUser
{
    self = [super init];
    if (self)
    {
        _message = messageForUser;
        NSLog( @"from constructor: %@",_message);
    }
    return self;

}

- (void)dealloc {
    NSLog(@"Goodbye cruel world. One WPMyObject signing off.");
   // [message release]; // ARC forbiddeth thee! Begone release.
    _message = nil;

   // [super dealloc]; // ARC forbiddeth explicit super dealloc
}

只是想看看我是否可以,我尝试在 dealloc 方法中调用 [super dealloc],但 ARC 会因错误而阻止您,这很好,因为它会为您执行此操作。但是,当我编写自己的 init 方法时,它并没有阻止我。

当我在 XCode 中使用“Run”运行程序时,我得到了“再见残酷世界”NSLog 消息,正如我希望的那样,当运行 dealloc 时,但我也得到了这个实例仍然存在于运行结束,当使用内存泄漏模板进行 Instruments 分析时:

如果我在没有下面的实例创建代码的情况下运行,我只会报告一些标准库 malloc 泄漏,但如果我添加此代码,则会发生所有泄漏:

  WPMyObject * myObject = [[WPMyObject alloc] initWithMessage: @"Hello World!\n" ];

这是我所看到的,我认为我理解的是告诉我 WPMyObject 的唯一实例正在泄漏:

完整的源代码非常简单(一个使用 Objective-C 和Foundation/Foundation.h 的小型 Mac OS X 命令行应用程序)在 BitBucket 上。点击this link在浏览器中查看源代码。

main.m 单元在 @autoreleasepool {...} 上下文语句中运行单个测试对象实例创建:

如果您想在自己的计算机上查看完全微不足道的代码,请按以下方式获取:

  hg clone https://bitbucket.org/wpostma/objectivecplaymac

更新:您可以通过在 } 之前添加这行代码来修复“泄漏”(可能是 Instruments 中的错误,或 clang/llvm 编译器错误,而不是“真正的泄漏”)结束自动释放池:

  NSLog( @"Reached end of autorelease pool" );

是的。添加日志消息。 “泄漏”消失。这是 Mac OS X 10.7.5 上的 XCode 4.5.2 (4G2008a),包含 Instruments 版本 4.5 (build 4523)。

Update2:这确实是一个简单的多进程竞争条件。下面的代码似乎是一个合理的调试构建位黑客。如果有更明确的说法“等到 Instruments 用我完成,然后退出 main()”,那可能是一个不错的未来功能,Apple 工程师。 PROFILER_SYNC("MESSAGE") 宏在发布模式下扩展为空,但在调试版本中,将“MESSAGE”发送到分析器......确实非常方便。

int main(int argc, const char * argv[])
{
    // This creates and cleans up an auto-release pool context.
    @autoreleasepool {

        foo(); // Microscopic amounts of debug code you want profiler to analyze go here.

#ifdef DEBUG
        usleep(10000); // race condition prevention in debug builds, so Instruments can finish up.
#endif

    }
#ifdef DEBUG
    usleep(10000); // race condition prevention in debug builds, so Instruments can finish up.
#endif

   return 0;
}

【问题讨论】:

    标签: ios objective-c cocoa automatic-ref-counting instruments


    【解决方案1】:

    听起来不像是泄漏,更像是一种竞争条件。 NSLog 的编写和程序的执行被终止之间的竞争。或者缓冲问题,更有可能是在达到某个阈值之前不会刷新输出。

    尝试在自动释放池的右大括号之后放置sleep(100);。或者尝试在dealloc 中的日志消息中添加更多文本。

    如果这不能“修复”它,则显示反汇编并查看两个代码版本之间的变化。


    发生这种情况的原因:出于性能原因,Instruments 和 NSLog() 都被有效地缓冲了。它们设计用于运行时间相对较长的进程,该进程几乎总是有一个主事件循环或对dispatch_main() 的调用。

    这样做是为了尽量减少对目标应用程序的性能的影响,但它可能会导致过程极其短暂的微基准测试出现异常。

    如果您在短暂的过程中有一个最小的测试用例,我建议用[[NSRunLoop currentLoop] run]; 关闭main()。这将永远运行,并为仪器和/或调试器提供一个可行的运行时。

    【讨论】:

    • Instruments 的 heap-watching-thread 和我的程序的主线程之间的竞争条件,基本上?因为这是我能想象的唯一两个线程/进程,因为我没有制作任何额外的用户线程......奇怪的是 NSLog 修复了它,但调用 CFShow(CFStr("something")) 没有,这意味着如果是竞争条件,NSLog 比 CFShow 慢很多。 :-)
    • sleep(100)(100 秒)可能有点矫枉过正。 :-) 更改为睡眠 100 毫秒,在我认为仪器可能需要一些时间来赶上动作的两个地方。亲爱的未来读者,如果愿意,可以将其翻倍并添加 11 个。
    猜你喜欢
    • 2015-09-14
    • 2023-04-10
    • 2011-12-06
    • 1970-01-01
    • 2012-12-16
    • 1970-01-01
    • 2012-01-05
    • 1970-01-01
    • 2014-06-15
    相关资源
    最近更新 更多