2

在您看到以下问题中的单词之前,请停止按retainCountOK ,请跳到底部的EDIT ,我已声明我已停止使用它。

我的 Cocoa App 使用MRR创建了许多全局资源,我在调用main()之前加载了这些资源。NSApplicationMain()由于NSApplicationMain()没有返回,我已经使用 对这些资源进行了清理atexit(),如下所示:

atexit(cleanup);

if (![CocoaUtil initCocoaUtil] ||
    ![PreferenceController initPreferenceController] ||
    ![ResourceManager initResourceManager])
{
    criticalAlertPanel(@"Failed to initialize application",
                       @"Failed to initialize application");
    return 4;
}

retval = NSApplicationMain(argc, (const char **)argv);

然而,在我的子类中的cleanup()任何视图被调用之前被调用(我没有日志消息来显示这一点),因此全局资源中对象的引用计数有时是. 我过于谨慎并试图通过使用这种方法来释放我的全局资源来预防内存泄漏:NSDocumentdealloc> 1

+ (void)fullRelease:(id)obj
             format:(NSString *)format, ...
{
    if (obj == nil)
        return;

    NSUInteger retainCount = [obj retainCount];
    if (retainCount > 1)
    {
        va_list va;
        va_start(va, format);
        NSString *objDesc = [[NSString alloc] initWithFormat:format arguments:va];
        logwrn(@"%@ has a reference count of %lu", objDesc, retainCount);
        [objDesc release];
    }

    while (retainCount > 0)
    {
        [obj release];
        retainCount--;
    }
}

我的日志显示以下内容:

12:15:04.954 INF -[AppController applicationDidFinishLaunching:] Application launched
12:15:06.702 INF -[AppController applicationShouldTerminate:] Application terminating
12:15:06.703 INF -[AppController applicationWillTerminate:] Application terminating
12:15:06.705 DBG cleanup Cleaning-up
12:15:06.705 INF +[ResourceManager finiResourceManager] Cleaning up
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[2] has a reference count of 2
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[3] has a reference count of 2
12:15:06.709 WRN +[CocoaUtil fullRelease:format:] _images[4] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[5] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[6] has a reference count of 2
12:15:06.710 WRN +[CocoaUtil fullRelease:format:] _images[7] has a reference count of 2
12:15:06.711 WRN +[CocoaUtil fullRelease:format:] _images[8] has a reference count of 2
12:15:06.711 WRN +[CocoaUtil fullRelease:format:] _images[9] has a reference count of 2
12:15:06.721 DBG +[PreferenceController finiPreferenceController] Cleaning up
12:15:06.721 DBG +[CocoaUtil finiCocoaUtil] Cleaning up

我的问题(终于!)是:

NSDocument有没有办法确保在所有实例都被销毁后清理我的全局资源并停止收到这些假阴性?

编辑:我已经取消了fullRelease调用,只是release对我的资源执行了正常操作,并且 Instruments 没有检测到任何内存泄漏,所以一切正常,但我很好奇为什么这些NSDocument对象在调用之前似乎没有被释放atexit()

4

4 回答 4

7

不要发布你不拥有的东西!

每个保留都属于其他人。仅发送release到对象以平衡对newalloccopyretain(NARC) 的调用。这种行为将不可避免地导致生产代码崩溃。

看起来你想确保一个对象被释放而不是简单地处理。如果您拥有该对象,请释放它一次;对它的其他引用属于其他对象。您的代码中可能确实存在内存泄漏(我们不能仅从此代码示例中判断),但通常可以通过静态分析器、仪器和一些肘部油脂找到这些泄漏。

更重要的是:当您的进程退出时,操作系统会为您释放所有内存。这不是 C 标准的一部分,但它只是 OS X 和 iOS 的操作方式,它是支持 Objective-C 的其他平台上的预期行为。因此,当您的进程退出时,您不必做任何特殊的清理工作,除非可能将文件写入磁盘或类似的东西。事实上,许多 Cocoa 应用程序不会费心去释放它们的应用程序委托所拥有的任何东西,因为让操作系统转储内存比调用-release数千个对象要快。

不要打电话-retainCount

它在说谎。干净利落。它包括 Cocoa 使用的临时引用,最重要的是,您永远不应该尝试干扰这些引用。-retainCount是一个有毒的符号。

于 2012-08-25T12:06:33.903 回答
7

一些注意事项:

  • 不要使用retainCount;有关详细信息,请参阅http://whentouseretaincount.com上的链接

  • atexit() 处理程序在高级编程中毫无用处。调用时,应用程序处于相对未定义的状态。框架将拆除一些东西,但正如您所指出的,将有大量的对象永远不会被释放。在某些情况下,可能根本不会调用 atexit()。

  • 您不能依赖应用程序终止来执行任何类型的所需状态清理。用户可能会强制退出您的应用程序。系统也可能会被强制重启。终止行为应被视为一种优化;你能做一些能让下一次发射更快的事情吗?

  • 在应用程序终止期间,无需释放任何东西。系统将在终止时回收所有应用程序资源,无论应用程序状态如何。换句话说,终止时留在内存中的对象不是泄漏

  • 一般来说,“泄漏”检测工具只能用于修复明显的问题。泄漏仅检测内存泄漏。它无法检测到内存增加,其中增加的对象仍以某种方式连接到全局。吸积在技术上不是泄漏,但它们很容易成为问题的巨大来源。

  • HeapShot 分析将检测泄漏和内存增加。

  • 考虑迁移您的应用以使用 ARC。ARC 的指导模式之一是编译器完全了解对象的生命周期。在 MRR 下不明确的表达式在 ARC 下是不允许的(在某些情况下没有适当的标记)。同样,ARC 编译器和分析器可以对您的代码进行更深入的分析,发现许多可能非常微妙的问题。

于 2012-08-25T16:50:50.660 回答
3

禁用突然终止,不要使用全局变量,只使用常规引用计数规则。在某些情况下,您需要中断强循环引用或手动清除对象的实例变量。最后,在 main 返回之前暂停/中断可能会更有帮助,然后运行heap以查看此阶段真正存在的内容(泄漏)。

我过于谨慎,并试图通过使用这种方法来释放我的全局资源来预防内存泄漏。

你不应该像这样清除 - 你知道得更好:)

但是,定位和销毁您可以控制的所有引用/对象/分配实际上是一种非常好的做法,以确保您的程序运行良好、可重用,并作为监控应用程序变化时回归的良好指标。

于 2012-08-25T12:39:35.317 回答
2

正如所有其他人所说:释放你拥有的东西。您的应用程序现在可能不会为您崩溃,但您只是走运了。当你这样做时,它可能会在另一台 Mac 上崩溃,或者在两个小时内,或者其他什么时候。

如果您有一个稀缺资源,某个对象持有该资源确实需要注销(例如,与另一台服务器的网络会话,如果未正确向其发送签核消息将导致服务器等待您,保留一些您返回的内部状态),您应该将其构建您的对象中,而不是在您的对象被释放时这样做。

例如,在您的应用程序退出之前,Mac OS X 会发送一个通知,即 NSApplicationWillTerminate。让你的对象为此注册,当它得到它时,让它向服务器发送再见(并记住它已经这样做了)。这样,当您的应用程序退出时,但例如应用程序委托仍持有该对象时,您仍然可以确定会发生签核。

但是您可能还应该检查您是否已经在-dealloc 中注销。有一天,您可能会添加对多个服务器的支持,然后当您发布一个服务器时,您希望它发送再见,即使您的应用程序不会退出。但只有在您还没有完成的情况下才进行签字。

当您在内存中有一些数据要在退出前写下时也是如此(例如,这就是 NSUserDefaults 所做的以确保快速访问首选项,但在您退出之前它仍会被写入磁盘)。

但这样的案例真的很少见。通常 NSDocument 会在需要时调用您写入磁盘,并且通常服务器会在连接断开时通知并自行清理。所以你真的不应该发布不属于你的东西。您只会导致某些用户在退出时崩溃。它给人的印象非常糟糕,并且可能导致您的应用程序在有机会保存一些数据之前实际上失败,从而使事情变得更糟。

于 2012-08-25T16:25:55.787 回答