Chr*_*ick 27 cocoa unit-testing memory-management objective-c ocunit
您将如何编写单元测试 - 例如使用OCUnit - 以确保在Cocoa/Objective-C中正确释放/保留对象?
一个天真的方法是检查它的价值retainCount
,但当然你永远不应该使用retainCount
.您是否可以简单地检查对象的引用是否被赋值nil
以指示它已被释放?另外,您对实际取消分配对象的时间有什么保证?
我希望只有几行代码的简洁解决方案,因为我可能会广泛使用它.实际上可能有两个答案:一个使用自动释放池,另一个不使用.
为了澄清,我不是在寻找一种全面测试我创建的每个对象的方法.全面地对任何行为进行单元测试是不可能的,更不用说内存管理了.但至少,最好检查已发布对象的行为以进行回归测试(并确保相同的与内存相关的错误不会发生两次).
我接受了BJ Homer的答案,因为我发现它是实现我想到的最简单,最简洁的方法,但需要注意的是,在XCode的生产版本中,提供自动引用计数的弱指针是不可用的.从2011年7月23日到4.2?)我也对这一点印象深刻
可以基于每个文件启用ARC; 它不要求整个项目使用它.您可以使用ARC编译单元测试并将主项目保留为手动保留释放,此测试仍然有效.
话虽如此,为了更详细地探讨Objective-C中单元测试内存管理所涉及的潜在问题,我强烈推荐Peter Hosey的深入回应.
Pet*_*sey 17
您是否可以简单地检查对象的引用是否被赋值
nil
以指示它已被释放?
不,因为向release
对象发送消息并分配nil
给变量是两个不同且无关的事情.
最接近的是,将任何内容分配给强/保留或复制属性(转换为访问者消息)会导致释放属性的先前值(由setter完成).即便如此,观察使用KVO的物业的价值,并不意味着你会知道物品什么时候被释放; 最重要的是,当取消分配拥有对象时,当它release
直接发送给拥有对象时,您将不会收到通知.您还将在控制台中收到警告消息(因为拥有对象在您观察时已经死亡),并且您不希望来自单元测试的嘈杂警告消息.另外,你必须专门观察每个物体的每个属性来拉动这个,并且你可能错过了一个bug.
release
对对象的消息对指向该对象的任何变量都没有影响.也没有解除分配.
这在ARC下略有变化:nil
当引用的对象消失时,将自动分配弱引用变量.但是,这并没有多大帮助,因为根据定义,强引用变量不会:如果对对象有强引用,则对象不会(好,不应该)消失,因为强引用将(应该)保持活力.在它之前死亡的物体应该是你正在寻找的问题之一,而不是你想要用作工具的东西.
理论上,您可以为您创建的每个对象创建一个弱引用,但您必须专门引用每个对象,在代码中手动创建一个变量.你可以想象,一个巨大的痛苦,肯定会错过物体.
另外,您对实际发布对象的时间有什么保证?
通过向对象发送release
消息来释放对象,因此在接收到该消息时释放该对象.
也许你的意思是"解除分配".释放只是让它更接近那一点; 如果每个版本仅仅平衡了之前的保留,那么一个对象可以多次释放并且仍然具有较长的寿命.
最后一次释放时,对象将被释放.这会立即发生.臭名昭着retainCount
甚至没有下降到0,因为许多试图写作的聪明人while ([obj retainCount] > 0) [obj release];
已经发现了.
实际上可能有两个答案:一个使用自动释放池,另一个不使用.
使用自动释放池的解决方案仅适用于自动释放的对象; 根据定义,未自动释放的对象不会进入池中.永远不会自动释放某些对象(特别是那些你创造了数千个对象的对象)是完全有效的,偶尔也是可取的.此外,您无法查看池中的内容以查看其中的内容和内容,或者尝试戳每个对象以查看它是否已经死亡.
您将如何编写单元测试 - 例如使用OCUnit - 以确保在Cocoa/Objective-C中正确释放/保留对象?
你能做的最好是设置NSZombieEnabled
于YES
在setUp
和恢复之前的值tearDown
.这将捕获过度释放/保留不足,但不会泄漏任何类型.
即使你可以编写一个彻底测试内存管理的单元测试,它仍然是不完美的,因为它只能测试可测试的代码模型对象和某些控制器.您可能仍然在应用程序中发生泄漏和崩溃,这是由视图代码,nib-borne引用和某些选项("关闭时释放")等引起的.
您可以编写没有应用程序外的测试,以确保您的应用程序没有内存错误.
也就是说,像你想象的那样,如果是自包含和自动的测试,那将是非常酷的,即使它无法测试一切.所以我希望我错了,有办法.
BJ *_*mer 14
如果您可以使用新引入的自动引用计数(在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);
}
Run Code Online (Sandbox Code Playgroud)
请注意,由于对语言语义的这种更改,这在ARC下工作:
可保留对象指针是空指针或指向有效对象的指针.
因此,设置指向nil的指针的行为保证释放它指向的对象,并且没有办法(在ARC下)释放对象而不删除指向它的指针.
需要注意的一点是,ARC可以基于每个文件启用; 它不要求整个项目使用它.您可以使用ARC编译单元测试并将主项目保留为手动保留释放,此测试仍然有效.
上面没有检测到过度释放,但NSZombieEnabled
无论如何都很容易被发现.
如果ARC根本不是一个选项,你可以做一些与Mike Ash相似的事情MAZeroingWeakRef
.我没有太多使用它,但它似乎以向后兼容的方式提供与__weak指针类似的功能.