在XCode 4.5中使用ARC的Phantom内存泄漏,其中dealloc被明确调用或者是一个Instruments问题?

War*_* P 6 cocoa objective-c instruments ios automatic-ref-counting

前言; 这不是一般的"我有一个漏洞的巨型应用程序"问题.这是一个特定的问题,关于自动引用计数在一个近乎琐碎的演示应用程序中无法正常工作,具有完整的源代码,或细微的代码生成或编译器问题,或者在工具中的错误.(TLDR:哦.实际上是一个奇怪的小竞争条件)

我感到困惑的是,仪器的"分配"列表显示了一个实例泄漏但是,我有一个该类的实例,只有一个,而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
}
Run Code Online (Sandbox Code Playgroud)

只是为了看我是否可以,我试着调用[super dealloc]dealloc方法,ARC阻止你一个错误,这很好,因为它会为你做这件事.但是当我编写自己的init方法时,它并没有阻止我.

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

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

  WPMyObject * myObject = [[WPMyObject alloc] initWithMessage: @"Hello World!\n" ];
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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

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

在此输入图像描述

如果你想在自己的计算机上看到完全琐碎的代码,请抓住它:

  hg clone https://bitbucket.org/wpostma/objectivecplaymac
Run Code Online (Sandbox Code Playgroud)

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

  NSLog( @"Reached end of autorelease pool" );
Run Code Online (Sandbox Code Playgroud)

是的.添加日志消息."泄漏"消失了.这是Mac OS X 10.7.5上的XCode 4.5.2(4G2008a),包含Instruments Version 4.5(build 4523).

Update2:这真的是一个简单的多进程竞争条件.下面的代码似乎是一个合理的Debug-build bit of hackery.如果有更明确的方式说"等到仪器完成,然后退出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;
}
Run Code Online (Sandbox Code Playgroud)

bbu*_*bum 3

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

sleep(100);尝试在自动释放池的右大括号后面添加一个。或者尝试向 .txt 文件中的日志消息添加更多文本dealloc

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


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

这样做是为了尽量减少对目标应用程序性能的影响,但它可能会导致微基准测试中出现奇怪的情况,其中该过程的生命周期非常短。

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