7

简短版本:

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

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

// 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.");
}

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

开始
初始化<我的视图:0x10013f840>
初始化我的视图<:0x10261b620>
运行
退出
dealloc<我的视图:0x10013f840>
结束。

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

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

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

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

就是这样!(呼)如果你做到了这一步,感谢你花时间阅读这一切。

4

3 回答 3

6

我想到了。解决方案是在方法中创建和发布我自己NSAutoreleasePoolapplicationWillTerminate:

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

实验结果:我在, , , 和for 方法中
添加了一堆日志消息,它们都有类似这样的代码:initretainreleaseautoreleaseMyView

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.

并且你可以清楚地看到当它耗尽时release发送到子视图的两条消息。NSAutoreleasePool

参考:来自 Apple 开发者文档的GNUStep
Autorelease
Pools的 NSView.m

于 2009-06-27T16:57:17.113 回答
3

这不仅仅是意见。这就是一切。我认为即使是 NSApplication 对象也不会自行释放。

事实上,我很确定(但不是 100% 确定)我的问题 #1 的答案是:“因为在应用程序即将终止时释放子视图是不必要的。”

我也相信。

如果您想在退出时发布您的自定义对象图,请让您的应用程序委托拥有它,并在 applicationWillTerminate: 中发布您的其他顶级对象。只要您正确管理所有所有权并从该方法释放每个顶级自定义对象,您的所有自定义对象(包括视图)都会消失。

注意:我没有尝试将它与 Core Data 混合。对您的托管对象可能会也可能不会这样做。我对此没有任何第一手经验。

于 2009-06-27T08:53:26.587 回答
1

在您提供的代码中,您将子视图添加到名为“视图”的 ivar。那是您真正所做的还是只是将代码复制到问题中?

我问这个是因为如果我为主窗口的内容视图创建一个 IBOutlet 并运行你的代码,它会按照你说的做。但是,如果我将 myView 本地 var 添加到 parentView 那么它确实会释放:

begin
init <MyView: 0x174460>
init <MyView: 0x174770>
run
quit
dealloc <MyView: 0x174460>
end
dealloc <MyView: 0x174770> 

此外,似乎子视图被自动释放(向自动释放添加日志消息证明了这一点)。

于 2009-06-27T16:16:26.413 回答