为什么当Cocoa应用程序终止时,NSView的子视图没有发送发布消息? [英] Why are subviews of an NSView not sent a release message when a Cocoa application terminates?

查看:214
本文介绍了为什么当Cocoa应用程序终止时,NSView的子视图没有发送发布消息?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简短版本


  1. 为什么NSView对象的子视图不会发送 release 消息时,Cocoa应用程序终止?
  2. 有没有办法重写这个行为?

示例:

MyView 下面显示的类只是一个 NSView 子类,当它被创建和销毁时会报告给控制台。我测试了它,发现它工作正常。但是,当我使用它,如我的应用程序委托的下一个代码片段所示,我看到一些意想不到的东西(见示例输出)。

  // MyView:

@interface MyView:NSView {}
@end

@implementation MyView

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

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

@end

  //应用程式委托:

- (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 > code>

init < MyView:0x10261b620 >

运行

退出

dealloc < MyView:0x10013f840 >

end。


问题: $ b我可以清楚地看到,当应用程序退出时,第一个视图对象被释放,并且我确定(测试和验证) NSView 对象自动释放他们的子视图他们自己被释放。



长期版本:(AKA为什么会有人关心?:)

让我开始说,我熟悉当一个正在运行的应用程序退出时,内存被释放的方式。我知道我的子视图将被正确处理,即使他们从来没有发送发布消息,所以我不担心这是一个泄漏。事实上,我很肯定(但不是100%肯定),我的问题#1的答案是:因为在应用程序即将终止时释放子视图是不必要的。



我使用一些简单的手动代码来做内存跟踪,而我的应用程序在调试模式下运行。我调用 init 中的 Trace_Init() Trace_Dealloc / code>和 dealloc 方法,我使用 atexit()函数在我的应用程序的Cocoa部分完成后报告任何未发布的对象。我发现这比定期运行Apple的内存泄漏性能工具要简单得多。如果我在运行时造成内存泄漏,一旦我的应用程序退出,我就会知道。



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



就是这样!如果你这样做了,谢谢你抽出时间阅读。

解决方案

我想出来了。解决方案是在 applicationWillTerminate:方法内创建和发布我自己的 NSAutoreleasePool



详情:

NSView dealloc 方法,所有类型的事情都是从响应器链中删除视图及其所有子视图,设置下一个键视图,发送委托消息等。在这段代码中,每个子视图发送保留消息,然后发送 autorelease 消息。 (实际上,每个子视图被保留并自动释放两次 - 见下面的细节)。这是正常的,但这里是踢球:当子视图发送一个 autorelease 消息,他们被添加到任何 NSAutoreleasePool 恰好在那个时间点是活动的,并且它们被保留,直到特定池超出范围。在应用程序终止的情况下,它们被添加到的池是在应用程序的主事件循环的每次迭代期间自动创建的池,并且该池从不被发送 release

实验结果

我添加了一堆日志消息到 init retain MyView 的方法 autorelease ,所有代码类似于此:

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

我也记录了 { <$ c $ c> c> c> c>。



使用这些日志消息,这里是我的 NSView obejcts:


发生的事情

  begin 
[parent init]:count = 1
[subview init]:count = 1
[subview retain]:count = 2
= 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 放弃);
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[parentView release];
[pool release];
NSLog(@end。);
}

结果如下:

  begin 
[parent init]:count = 1
[subview init]:count = 1
[ 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]
}
end。

你可以清楚地看到两个 release



参考文献: br>
NSView.m from GNUStep

Autorelease Pools ,来自Apple的开发人员文档


The short version:

  1. Why are the subviews of NSView objects not sent a release message when a Cocoa application terminates?
  2. Is there a way to override this behaviour?

An example:
The MyView class shown below is nothing more than an NSView subclass that reports to the console when it is created and destroyed. I have tested it out and found it to work properly. However, when I use it as shown in the next code snippet from my application delegate, I see something unexpected (see sample output).

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

This application produces the following output:

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

The problem:
I can clearly see that the first view object is being released when the application quits, and I'm certain (tested and verified) that NSView objects automatically release their subviews when they themselves are released. However, it appears that during application termination those subviews are not being released.

The long version: (AKA why on earth would anyone care? :)
Let me start by saying that I am familiar with the way memory is freed by a running application when it quits. I know that my subviews will be properly disposed of, even if they are never sent a release message, so I'm not worried about this being a leak. In fact, I'm pretty sure (but not 100% certain) that the answer to my question #1 is: "Because releasing subviews is unnecessary when the application is about to terminate."

I use some simple hand-rolled code to do memory tracking while my application is running in debug mode. I make a calls to a Trace_Init() and Trace_Dealloc() method in the init and dealloc methods of all of my custom classes, and I use the atexit() function to report any unreleased objects after the Cocoa portion of my application has finished. I find this to be much simpler than running Apple's memory leak performance tool on a regular basis. If I cause a memory leak while running, I'll know about it as soon as my application quits.

However, the lack of a dealloc call during termination means that any of my custom NSView subclasses that are used as subviews show up as memory leaks when I quit the application. Thus the reason for my question #2. I would like to have Cocoa release everything during termination so that my memory tracking is able to wrap up properly. Naturally, I would only override the default behaviour in debug mode. My released app has none of the memory tracking code enabled, and should be able to quit itself as efficiently as normal.

That's it! (phew) If you made it this far, thank you for taking the time to read it all.

解决方案

I figured it out. The solution was to create and release my own NSAutoreleasePool within the applicationWillTerminate: method.

Details:
Deep in the bowels of NSView's dealloc method, all kinds of things are done to remove the view and all of its subviews from the responder chain, set up the next key view, send delegate messages, etc. Somewhere in this code, each subview is sent a retain message, and later sent an autorelease message. (Actually, each subview is retained and autoreleased twice - see details below). This is normal, but here's the kicker: When the subviews are sent an autorelease message, they get added to whatever NSAutoreleasePool happens to be active at that point in time, and they are kept around until that particular pool goes out of scope. In the case of application termination, the pool they get added to is the one created automatically during each iteration of the application's main event loop, and this pool is never sent a release message because the application is about to quit!

Experimental results:
I added a bunch of logging messages to the init, retain, release, and autorelease methods for MyView, which all have code similar to this:

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

I also logged { } around the code for dealloc so I could see when the magic happens.

Using these logging messages, here is what happens to my NSView obejcts:

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.

Now, when I use the following code in applicationWillTerminate:

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

The result is as follows:

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.

And you can clearly see the two release messages sent to the subview by the NSAutoreleasePool as it drains.

References:
NSView.m from GNUStep
Autorelease Pools from Apple's developer documentation

这篇关于为什么当Cocoa应用程序终止时,NSView的子视图没有发送发布消息?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆