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)
听起来不太像泄漏,而更像是竞争条件。写入NSLog和程序执行之间的竞争被终止。或者更有可能是缓冲问题,在达到某个阈值之前不会刷新输出。
sleep(100);尝试在自动释放池的右大括号后面添加一个。或者尝试向 .txt 文件中的日志消息添加更多文本dealloc。
如果这不能“修复”它,则显示反汇编代码并查看两个版本的代码之间有什么变化。
发生这种情况的原因是:出于性能原因,Instruments 和 NSLog() 都被有效缓冲。它们设计用于运行时间相对较长的进程,该进程几乎总是具有主事件循环或对 的调用dispatch_main()。
这样做是为了尽量减少对目标应用程序性能的影响,但它可能会导致微基准测试中出现奇怪的情况,其中该过程的生命周期非常短。
如果您在短暂的过程中有一个最小的测试用例,我建议main()关闭[[NSRunLoop currentLoop] run];. 这将永远运行,并为仪器和/或调试器提供可行的运行时。