[英]Unit tests for memory management in Cocoa/Objective-C
您将如何编写单元测试(例如,使用OCUnit )以确保对象在 Cocoa/Objective-C 中被正确释放/保留?
一个天真的方法是检查retainCount
的值,但当然你不应该使用retainCount
。 你能简单地检查一个对象的引用是否被赋值为nil
来表示它已经被释放了吗? 此外,您对实际释放对象的时间有什么保证?
我希望只有几行代码的简洁解决方案,因为我可能会广泛使用它。 实际上可能有两个答案:一个使用自动释放池,另一个不使用。
澄清一下,我不是在寻找一种方法来全面测试我创建的每个 object。 不可能对任何行为进行全面的单元测试,更不用说 memory 管理了。 不过,至少,最好检查已发布对象的行为以进行回归测试(并确保相同的与内存相关的错误不会发生两次)。
我接受了BJ Homer的回答,因为我发现它是完成我的想法的最简单、最简洁的方法,因为需要注意的是自动引用计数提供的弱指针在 XCode 的生产版本中不可用(之前到 4.2?)截至 2011 年 7 月 23 日。得知这一点我也印象深刻
ARC 可以在每个文件的基础上启用; 它不需要您的整个项目都使用它。 您可以使用 ARC 编译您的单元测试并将您的主项目保留在手动保留发布上,并且该测试仍然可以工作。
话虽如此,为了更详细地探索 Objective-C 中单元测试 memory 管理所涉及的潜在问题,我强烈推荐Peter Hosey 的深入回应。
你能简单地检查一个对象的引用是否被赋值为
nil
来表示它已经被释放了吗?
不,因为向 object 发送release
消息并将nil
分配给变量是两个不同且不相关的事情。
您可以获得的最接近的是,将任何内容分配给强/保留或复制属性,这会转换为访问器消息,导致该属性的先前值被释放(由设置器完成)。 即便如此,观察财产的价值——比如使用 KVO——并不意味着你会知道 object 什么时候发布; 尤其是当拥有的 object 被释放时,当它直接向拥有的 object 发送release
时,您不会收到通知。 您还将在控制台中收到一条警告消息(因为拥有 object 在您观察它时死亡),并且您不希望来自单元测试的嘈杂警告消息。 另外,您必须专门观察每个 object 的每个属性才能完成此任务——错过一个,您可能会错过一个错误。
发给 object 的release
消息对指向该 object 的任何变量都没有影响。 释放也不行。
这在ARC下略有变化:当引用的 object 消失时,弱引用变量将自动分配nil
。 但是,这对您没有多大帮助,因为根据定义,强引用变量不会:如果强引用 object,则 object不会(好吧,不应该) Z34D1F91FB2E514B8576ZFAB1A将(应该)让它活着。 object 在它应该之前死亡是您正在寻找的问题之一,而不是您想要用作工具的东西。
理论上,您可以创建对您创建的每个 object 的弱引用,但您必须专门引用每个 object,在代码中手动为其创建一个变量。 正如你可以想象的那样,巨大的痛苦和肯定会错过物体。
此外,您对实际释放对象的时间有什么保证?
object 通过向其发送release
消息来释放,因此 object 在收到该消息时被释放。
也许您的意思是“解除分配”。 释放只是让它更接近那个点。 一个 object 可以多次发布,如果每次发布只是平衡了之前的保留,它仍然有很长的寿命。
object 在最后一次释放时被释放。 这会立即发生。 臭名昭著的retainCount
甚至没有将 go 降至 0,就像许多聪明人试图写while ([obj retainCount] > 0) [obj release];
已经发现。
实际上可能有两个答案:一个使用自动释放池,另一个不使用。
使用自动释放池的解决方案仅适用于自动释放的对象; 根据定义,未自动释放的对象不会 go 进入池中。 永远不要自动释放某些对象(尤其是您创建的成千上万个对象)是完全有效的,有时也是可取的。 此外,您无法查看池中的内容和没有的内容,或者尝试戳每个 object 以查看它是否已死。
您将如何编写单元测试(例如,使用 OCUnit)以确保对象在 Cocoa/Objective-C 中被正确释放/保留?
您可以做的最好的事情是在setUp
中将NSZombieEnabled
设置为YES
并在tearDown
中恢复其先前的值。 这将捕获过度释放/保留不足,但不会捕获任何类型的泄漏。
即使你可以编写一个单元测试来彻底测试 memory 管理,它仍然是不完美的,因为它只能测试可测试的代码——模型对象,可能还有某些控制器。 您的应用程序中仍然可能存在由视图代码、nib-borne 引用和某些选项(想到“Release When Closed”)等引起的泄漏和崩溃。
您可以编写任何应用程序外测试来确保您的应用程序没有内存错误。
也就是说,像你想象的那样的测试,如果它是独立的和自动的,即使它不能测试所有东西,它也会很酷。 所以我希望我错了,有办法。
如果您可以使用新引入的自动引用计数(Xcode 的生产版本中尚不可用,但在此处记录),那么您可以使用弱指针来测试是否有任何内容被过度保留。
- (void)testMemory {
__weak id testingPointer = nil;
id someObject = // some object with a 'foo' property
@autoreleasepool {
// Point the weak pointer to the thing we expect to be dealloc'd
// when we're done.
id theFoo = [someObject theFoo];
testingPointer = theFoo;
[someObject setTheFoo:somethingElse];
// At this point, we still have a reference to 'theFoo',
// so 'testingPointer' is still valid. We need to nil it out.
STAssertNotNil(testingPointer, @"This will never happen, since we're still holding it.")
theFoo = nil;
}
// Now the last strong reference to 'theFoo' should be gone, so 'testingPointer' will revert to nil
STAssertNil(testingPointer, @"Something didn't release %@ when it should have", testingPointer);
}
请注意,由于语言语义发生了这种变化,这在 ARC 下有效:
可保留的 object 指针是 null 指针或指向有效 object 的指针。
因此,将指针设置为 nil 的行为可以保证释放它指向的 object,并且没有办法(在 ARC 下)在不删除指向它的指针的情况下释放 object。
需要注意的一点是 ARC 可以在每个文件的基础上启用。 它不需要您的整个项目都使用它。 您可以使用 ARC 编译您的单元测试并将您的主项目保留在手动保留发布上,并且该测试仍然可以工作。
上面没有检测到过度释放,但无论如何使用NSZombieEnabled
很容易捕捉到。
如果 ARC 根本不是一个选项,您可以使用 Mike Ash 的MAZeroingWeakRef
做类似的事情。 我用的不多,但它似乎以向后兼容的方式提供了与 __weak 指针类似的功能。
这可能不是您想要的,但作为一个思想实验,我想知道这是否可能会做一些接近您想要的事情:如果您创建一个机制来跟踪您想要测试的特定对象的保留/释放行为会怎样。 像这样工作:
CFMutableSetRef
并设置一个自定义保留/释放函数以什么都不做registerForRRTracking: (id) object
clearRRTrackingReportingLeaks: (BOOL) report
,该报告将在该时间点报告集合中的任何 object。[tracker clearRRTrackignReportingLeaks: NO];
在单元测试开始时[tracker clearRRTrackingReportingLeaks: YES];
它会列出所有未正确处理的对象。 您也可以覆盖NSObject alloc
并跟踪所有内容,但我想您的集合会变得过大(.!!)。
更好的办法是将CFMutableSetRef
放在一个单独的进程中,因此它不会过多地影响您的程序运行时 memory 占用空间。 虽然增加了进程间通信的复杂性和运行时影响。 可以使用私有堆(或区域 - 那些还存在吗?)将其隔离到较小程度。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.