繁体   English   中英

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

[英]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 指针类似的功能。

这可能不是您想要的,但作为一个思想实验,我想知道这是否可能会做一些接近您想要的事情:如果您创建一个机制来跟踪您想要测试的特定对象的保留/释放行为会怎样。 像这样工作:

  1. 创建 NSObject dealloc 的覆盖
  2. 创建一个CFMutableSetRef并设置一个自定义保留/释放函数以什么都不做
  3. 制作一个像registerForRRTracking: (id) object
  4. 制作一个单元测试例程,如clearRRTrackingReportingLeaks: (BOOL) report ,该报告将在该时间点报告集合中的任何 object。
  5. 调用[tracker clearRRTrackignReportingLeaks: NO]; 在单元测试开始时
  6. 为您要跟踪的每个 object 在单元测试中调用 register 方法,它会在 dealloc 时自动删除。
  7. 在测试结束时调用[tracker clearRRTrackingReportingLeaks: YES]; 它会列出所有未正确处理的对象。

您也可以覆盖NSObject alloc并跟踪所有内容,但我想您的集合会变得过大(.!!)。

更好的办法是将CFMutableSetRef放在一个单独的进程中,因此它不会过多地影响您的程序运行时 memory 占用空间。 虽然增加了进程间通信的复杂性和运行时影响。 可以使用私有堆(或区域 - 那些还存在吗?)将其隔离到较小程度。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM