7

短いバージョン:

  1. releaseCocoa アプリケーションの終了時 に 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.");
}

このアプリケーションは、次の出力を生成します。

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

問題:
アプリケーションの終了時に最初のビュー オブジェクトが解放されていることがはっきりとわかります。また、NSViewオブジェクト自体が解放されると、オブジェクトがサブビューを自動的に解放する (テストおよび検証済み) ことを確信しています。ただし、アプリケーションの終了時に、これらのサブビューが解放されていないようです。

長いバージョン: (別名、なぜ一体誰が気にするのでしょうか? :)
まず、実行中のアプリケーションが終了したときにメモリが解放される方法に精通していると言っておきましょう。メッセージが送信されなくても、サブビューが適切に破棄されることはわかっているreleaseので、これがリークであるとは心配していません。実際、私の質問 #1 に対する答えは、「アプリケーションが終了しようとしているときにサブビューを解放する必要がないため」であると確信しています (ただし 100% 確実ではありません)。

アプリケーションがデバッグ モードで実行されている間、単純な手巻きのコードを使用してメモリ トラッキングを行います。すべてのカスタム クラスの and メソッドで and メソッドを呼び出し、この関数を使用してTrace_Init()、アプリケーションTrace_Dealloc()のCocoa部分が終了した後に未リリースのオブジェクトを報告します。これは、Apple のメモリ リーク パフォーマンス ツールを定期的に実行するよりもはるかに簡単だと思います。実行中にメモリ リークが発生した場合は、アプリケーションが終了するとすぐにわかります。initdeallocatexit()

ただし、dealloc終了時に呼び出しがないということはNSView、サブビューとして使用されているカスタム サブクラスのいずれかが、アプリケーションを終了したときにメモリ リークとして表示されることを意味します。したがって、私の質問#2の理由。終了時に Cocoa がすべてを解放して、メモリ追跡が適切に終了できるようにしたいと考えています。当然、デバッグ モードでのデフォルトの動作のみをオーバーライドします。私のリリースしたアプリでは、メモリ追跡コードが有効になっておらず、通常どおり効率的に終了できるはずです。

それでおしまい!(ふぅ) ここまで読んでくれてありがとう。

4

3 に答える 3

6

私はそれを考え出した。解決策は、メソッドNSAutoreleasePool内で独自のものを作成してリリースすることでした。applicationWillTerminate:

詳細:のメソッド
の奥深くで、ビューとそのすべてのサブビューをレスポンダー チェーンから削除し、次のキー ビューを設定し、デリゲート メッセージを送信するなど、あらゆる種類の処理が行われます。このコードのどこかで、それぞれサブビューにメッセージが送信され、後でメッセージが送信されます。(実際には、各サブビューは保持され、2 回自動解放されます - 以下の詳細を参照してください)。これは正常な動作ですが、重要な点は次のとおりです。サブビューにメッセージが送信されると、その時点でアクティブになっているものにサブビューが追加され、その特定のプールが範囲外になるまで保持されます。アプリケーションが終了した場合、それらが追加されるプールは、アプリケーションのメイン イベント ループの各反復中に自動的に作成されるプールです。NSViewdeallocretainautoreleaseautoreleaseNSAutoreleasePoolアプリケーションが終了しようとしているため、このプールには release メッセージが送信されません。

実験結果:の、、、およびメソッドに
一連のログ メッセージを追加しました。これらはすべて、次のようなコードを持っています。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されるときに によってサブビューに送信された 2 つのメッセージをはっきりと確認できます。NSAutoreleasePool

参考文献: Apple の開発者向けドキュメントのGNUStep Autorelease Poolsの
NSView.m

于 2009-06-27T16:57:17.113 に答える
3

ビューだけではありません。それはすべてです。NSApplication オブジェクトでさえ、それ自体を解放するとは思いません。

実際、私の質問 #1 に対する答えは、「アプリケーションが終了しようとしているときにサブビューを解放する必要がないため」であると確信しています (ただし 100% 確実ではありません)。

私もそう信じています。

終了時にカスタム オブジェクト グラフを解放する場合は、アプリ デリゲートにそれを所有させ、applicationWillTerminate: で他のトップレベル オブジェクトを解放します。すべての所有権を正しく管理し、そのメソッドからすべてのトップレベルのカスタム オブジェクトを解放する限り、ビューを含むすべてのカスタム オブジェクトは消滅します。

注: これを Core Data と混ぜてみたことはありません。管理対象オブジェクトに対してこれを実行できる場合とできない場合があります。私はそれについて直接の経験はありません。

于 2009-06-27T08:53:26.587 に答える
1

あなたが提示したコードでは、「ビュー」と呼ばれる ivar にサブビューを追加しています。それはあなたが本当にしたことですか、それともコードを質問にコピーしただけですか?

メイン ウィンドウのコンテンツ ビューに IBOutlet を作成し、コードを実行すると、あなたの言うことが実行されるためです。しかし、myView ローカル変数を parentView に追加すると、割り当てが解除されます。

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

さらに、サブ ビューが自動解放されるようです (自動解放にログ メッセージを追加すると、それが証明されます)。

于 2009-06-27T16:16:26.413 に答える