为什么在Cocoa应用程序终止时,NSView的子视图未发送释放消息?

时间:2009-06-27 07:24:35

标签: objective-c cocoa memory-management nsview

简短版本:

  1. 当Cocoa应用程序终止时,为什么NSView对象的子视图没有发送release消息?
  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.");
}

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

  

开始
  init < MyView:0x10013f840 >
  init < MyView:0x10261b620 >
  运行
  退出
  dealloc < MyView:0x10013f840 >
  端。

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

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

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

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

就是这样!(phew)如果你做到这一点,谢谢你花时间阅读全部内容。

3 个答案:

答案 0 :(得分:6)

我明白了。解决方案是在NSAutoreleasePool方法中创建和发布我自己的applicationWillTerminate:

<强>详细信息:
NSView的{​​{1}}方法的深处,完成了各种各样的事情来从响应者链中删除视图及其所有子视图,设置下一个关键视图,发送委托消息,在此代码的某处,每个子视图都会发送一条dealloc消息,然后发送retain消息。 (实际上,每个子视图都会被保留并自动释放两次 - 请参阅下面的详细信息)。这是正常的,但这里是踢球者:当子视图发送了autorelease消息时,它们会被添加到该时间点恰好处于活动状态的任何autorelease,并且它们会被保留到该特定状态游泳池超出范围。在应用程序终止的情况下,它们添加到的池是在应用程序的主事件循环的每次迭代期间自动创建的池,并且此池永远不会发送 NSAutoreleasePool 消息因为应用程序即将退出!

实验结果:
我向release的{​​{1}},initretainrelease方法添加了大量日志消息,这些方法都具有与此类似的代码:< / p>

autorelease

我还在MyView的代码周围记录了NSLog(@"[%@ retain]: count = %d", [self name], [self retainCount]+1); return [super retain]; {,这样我才能看到魔法何时发生。

使用这些日志消息,我的} obejcts:

会发生什么
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. 条消息。

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

答案 1 :(得分:3)

这不仅仅是观点。这就是一切。我甚至不认为NSApplication对象会自行释放。

  

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

我也相信。

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

注意:我没有尝试将其与Core Data混合使用。可能会也可能无法对托管对象执行此操作。我对此没有任何第一手经验。

答案 2 :(得分:1)

在您提供的代码中,您将子视图添加到名为“view”的ivar中。这是你真正做过的,还是仅仅是从代码复制到问题?

我问这个是因为如果我在主窗口的内容视图中创建一个IBOutlet并运行你的代码就会按照你说的去做。但是如果我将myView local var添加到parentView,那么它会解除分配:

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

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