objective-c - 为什么 NSView 的 subview 在 Cocoa 应用程序终止时不发送释放消息?

标签 objective-c cocoa memory-management nsview

简短版:

  1. 为什么 NSView 对象的 subview 没有发送 release Cocoa 应用程序终止时的消息?
  2. 有没有办法覆盖这种行为?

一个例子:
MyView下面显示的类只不过是一个 NSView在创建和销毁时向控制台报告的子类。我已经对其进行了测试,发现它可以正常工作。但是,当我按照我的应用程序委托(delegate)的下一个代码片段所示使用它时,我看到了一些意想不到的东西(参见示例输出)。

// MyView:

@interface MyView : NSView { }
@end

@implementation MyView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) == nil) { return nil; }
    NSLog(@"init %@", self);
    return self;
}

- (void)dealloc
{
    NSLog(@"dealloc %@", self);
    [super dealloc];
}

@end

// Application delegate:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSLog(@"begin");

    parentView = [[MyView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];

    MyView * myView = [[MyView alloc] initWithFrame:NSMakeRect(10, 10, 80, 80)];
    [parentView addSubview:myView];
    [myView release];

    NSLog(@"run");
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
    NSLog(@"quit");
    [parentView release];
    NSLog(@"end.");
}

此应用程序产生以下输出:

begin
init <MyView: 0x10013f840>
init <MyView: 0x10261b620>
run
quit
dealloc <MyView: 0x10013f840>
end.

问题:
我可以清楚地看到第一个 View 对象在应用程序退出时被释放,并且我确定(测试和验证)NSView对象在释放它们自己时会自动释放它们的 subview 。但是,在应用程序终止期间,这些 subview 似乎没有被释放。

长版:(也就是为什么会有人关心?:)
首先让我说,我熟悉正在运行的应用程序退出时释放内存的方式。我知道我的 subview 将被妥善处理,即使它们从未发送过 release。消息,所以我不担心这是泄漏。事实上,我非常确定(但不是 100% 确定)我的问题 #1 的答案是:“因为当应用程序即将终止时释放 subview 是不必要的。”

当我的应用程序在 Debug模式下运行时,我使用一些简单的手动代码来进行内存跟踪。我调用 Trace_Init()Trace_Dealloc() init 中的方法和 dealloc我所有自定义类的方法,我使用 atexit()在我的应用程序的 Cocoa 部分完成后报告任何未释放对象的函数。我发现这比定期运行 Apple 的内存泄漏性能工具要简单得多。如果我在运行时造成内存泄漏,我会在我的应用程序退出时立即知道。

但是,缺少一个dealloc终止期间的调用意味着我的任何自定义 NSView当我退出应用程序时,用作 subview 的子类显示为内存泄漏。这就是我提出问题 #2 的原因。我想让 Cocoa 在终止期间释放所有内容,以便我的内存跟踪能够正确结束。自然地,我只会覆盖 Debug模式下的默认行为。我发布的应用程序没有启用任何内存跟踪代码,应该能够像往常一样高效地自行退出。

就这些了!(呸)如果你读到这里,感谢你花时间阅读全部内容。

最佳答案

我想通了。解决方案是在 applicationWillTerminate: 方法中创建并释放我自己的 NSAutoreleasePool

详情:
NSViewdealloc 方法的深处,做了各种各样的事情来从响应者链中删除 View 及其所有 subview ,设置下一个键查看,发送委托(delegate)消息等。在这段代码的某处,每个 subview 都被发送了一个retain 消息,然后发送了一个autorelease 消息。 (实际上,每个 subview 都被保留和自动释放两次 - 请参阅下面的详细信息)。这是正常的,但问题在于:当向 subview 发送 autorelease 消息时,它们会添加到当时恰好处于事件状态的任何 NSAutoreleasePool 中,并且它们一直保留到该特定池超出范围为止。在应用程序终止的情况下,它们被添加到的池是在应用程序的主事件循环的每次迭代期间自动创建的池,并且这个池永远不会发送 release 消息,因为应用程序即将退出!

实验结果:
我在 initretainreleaseautorelease 方法中添加了一堆日志消息MyView,里面都有类似这样的代码:

NSLog(@"[%@ retain]:  count = %d", [self name], [self retainCount]+1);
return [super retain];

我还围绕 dealloc 的代码记录了 { },这样我就可以看到魔法何时发生。

使用这些日志消息,我的 NSView 对象发生了以下变化:

begin  
[parent init]:        count = 1
[subview init]:        count = 1
[subview retain]:      count = 2
[subview release]:     count = 1
run
quit
[parent release]:     count = 0
[parent dealloc]
{
    [subview retain]:      count = 2
    [subview autorelease]: count = 2
    [subview retain]:      count = 3
    [subview autorelease]: count = 3
    [subview release]:     count = 2
}
end.

现在,当我在 applicationWillTerminate:

中使用以下代码时
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
    NSLog(@"quit");
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    [parentView release];
    [pool release];
    NSLog(@"end.");
}

结果如下:

begin  
[parent init]:        count = 1
[subview init]:        count = 1
[subview retain]:      count = 2
[subview release]:     count = 1
run
quit
[parent release]:     count = 0
[parent dealloc]
{
    [subview retain]:      count = 2
    [subview autorelease]: count = 2
    [subview retain]:      count = 3
    [subview autorelease]: count = 3
    [subview release]:     count = 2
}
[subview release]:     count = 1
[subview release]:     count = 0
[subview dealloc]
{
}
end.

您可以清楚地看到 NSAutoreleasePool 在耗尽时发送到 subview 的两条 release 消息。

引用资料:
NSView.m来自 GNUStep
Autorelease Pools来自 Apple 的开发者文档

关于objective-c - 为什么 NSView 的 subview 在 Cocoa 应用程序终止时不发送释放消息?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1052304/

相关文章:

objective-c - 从 Objective-C 调用 Swift 单例

objective-c - 为自定义类中的全局变量设置默认值?

cocoa - 使用 UIKit 模仿 NSOutlineView

objective-c - 什么条件会导致 iVar 改变其类型?

ios - PNG 图像从未解除分配

objective-c - 找不到 keyPath : '' 的对象映射

Objective-C 和 Quartz Composer; [qcView PauseRendering] 导致 Bad_Access

c++ - 是否存在不在其分配中存储元数据的自定义内存分配器设计模式?

ios - 如何在 iOS 8/9 中获取今日扩展的宽度

objective-c - 如何使用 NSOutlineVIew 实现拖放