Cocoa/Objective-C中内存管理的单元测试

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中正确释放/保留对象?

你能做的最好是设置NSZombieEnabledYESsetUp和恢复之前的值tearDown.这将捕获过度释放/保留不足,但不会泄漏任何类型.

即使你可以编写一个彻底测试内存管理的单元测试,它仍然是不完美的,因为它只能测试可测试的代码模型对象和某些控制器.您可能仍然在应用程序中发生泄漏和崩溃,这是由视图代码,nib-borne引用和某些选项("关闭时释放")等引起的.

您可以编写没有应用程序外的测试,以确保您的应用程序没有内存错误.

也就是说,像你想象的那样,如果是自包含和自动的测试,那将是非常酷的,即使它无法测试一切.所以我希望我错了,有办法.

  • 我羡慕你的写作耐力.:) (2认同)

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指针类似的功能.