5

これをできるだけ簡潔に書き留めようとしていますが、説明するのは簡単ではありません-読んでくれてありがとう=)

私は、オープン ソース iPhone フレームワークSparrowの主な開発者です。Sparrow は Flash AS3 ライブラリをモデルにしているため、AS3 と同様のイベント システムを備えています。現在、そのシステムはセレクターを指定することで機能しますが、イベント リスナーにブロックを使用できるようにすることで、そのシステムを拡張したいと考えています。ただし、メモリ管理の問題につまずいています。

現在処理されているイベントの典型的な使用例を紹介します。

// init-method of a display object, inheriting from 
// the base event dispatcher class
- (id)init
{
    if (self = [super init])
    {
        // the method 'addEventListener...' is defined in the base class
        [self addEventListener:@selector(onAddedToStage:)
                      atObject:self
                       forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
    }
    return self;
}

// the corresponding event listener
- (void)onAddedToStage:(SPEvent *)event
{
    [self startAnimations]; // call some method of self
}

これは非常に簡単です。オブジェクトが表示リストに追加されると、イベントを受け取ります。現在、基本クラスはイベント リスナーを NSInvocation-objects の配列に記録します。NSInvocation は、そのターゲットと引数を保持しない方法で作成されます。(ユーザーはそれを行うことができますが、99% の場合は必要ありません)。

これらのオブジェクトが保持されないのは意識的な選択でした: そうしないと、ユーザーが dealloc-method でイベント リスナーを削除したとしても、上記のコードによってメモリ リークが発生します! 理由は次のとおりです。

- (id)init
{
    if (self = [super init])
    {
        // [self addEventListener: ...] would somehow cause:
        [self retain]; // (A)
    }
    return self;
}

// the corresponding event listener
- (void)dealloc
{
    // [self removeEventListener...] would cause:
    [self release]; // (B)
    [super dealloc];
}

一見すると、これで問題ないように見えます。init メソッドの保持は、dealloc メソッドのリリースと対になっています。ただし、保持カウントがゼロにならないため、dealloc メソッドが呼び出されることはないため、これは機能しません。

私が言ったように、「addEventListener...」メソッドは、まさにこの理由で、デフォルト バージョンでは何も保持しません。イベントの仕組み (ほとんどの場合、イベントは「自己」または子オブジェクトによってディスパッチされ、とにかく保持されます) のため、それは問題ではありません。

しかし、ここで問題の中心部分に行き着きます。ブロックではそれを行うことはできません。私が望むように、イベント処理のブロックバリアントを見てください:

- (id)init
{
    if (self = [super init])
    {
        [self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event)
        {
            [self startAnimations];
        }];
    }
    return self;
}

それは見栄えがよく、非常に使いやすいでしょう。ただし、ユーザーが「self」でメソッドを呼び出したり、ブロック内でメンバー変数を使用したりすると (ほとんどの場合)、ブロックは自動的に「self」を保持し、オブジェクトの割り当てが解除されることはありません。 .

今、私は、次のように自己への __block 参照を作成することで、任意のユーザーがこれを修正できることを知っています:

__block id blockSelf = self;
[self addEventListenerForType:ADDED_TO_STAGE block:^(SPEvent *event)
{
    [blockSelf startAnimations];
}];

しかし、正直なところ、ほとんどすべてのユーザーがそうするのを知らないか、そうするのを忘れていると確信しています。API は使いやすいだけでなく、誤用されにくいものであるべきですが、これは明らかにその原則に違反しています。API のユーザーは間違いなく誤用します。

私を悩ませているのは、「自己」を保持する必要がないことを知っていることです。現在の実装では、保持しなくても機能します。だからは、彼が自分自身を保持する必要がないことをブロックに伝えたい - 私、ライブラリは、ユーザーがそれについて考える必要がないように、ブロックにそれを伝える必要がある.

私の研究では、そうする方法は見つかりませんでした。そして、そのブロックの制限に合わせてアーキテクチャを変更する方法が思いつきません。

私がそれについて何ができるか考えている人はいますか?
まだ読んでいない場合でも、ここまで読んでくれてありがとう -- 冗長な質問だったことは承知しています ;-)

4

2 に答える 2

5

このトピックについて Apple サポートと話し合ったところ、彼らは私が予想していたことを教えてくれました。現在、ブロックが自己を保持すべきではないことをブロックに伝える方法はありません。ただし、この問題を回避する方法は 2 つあります。

1 つ目は、ブロック変数ではなく、ブロックへの引数として self を渡すことです。API は次のようになります。

[self addEventListenerForType:ADDED_TO_STAGE 
                        block:^(id selfReference, SPEvent *event)

したがって、API は (保持されていない) 自己を渡す責任があります。開発者は、self の代わりにこの参照を使用する必要があることを伝える必要がありますが、少なくとも使いやすいでしょう。

この種の状況で Apple が使用したもう 1 つの解決策は、リスナーをシャットダウンするために release/dealloc とは別のメソッドを提供することです。これの良い例は、NSTimer の「無効化」メソッドです。これは、NSRunLoop と NSTimer の間のメモリ サイクルが原因で作成されました (設計による)。

于 2010-11-27T20:05:50.523 に答える
1

デザインは根本的に欠陥があるか、ブロックされているか、ブロックされていないと思います。オブジェクトが完全に初期化される前に、イベントリスナーとしてオブジェクトを追加しています。同様に、オブジェクトは、割り当てが解除されている間もイベントを受信する可能性があります。イベントリスナーの追加と削除は、割り当て/割り当て解除IMOから切り離す必要があります。

あなたの当面の問題に関しては、あなたはそれについて心配する必要がないかもしれません。ブロック内でオブジェクトを参照することにより、オブジェクトへの参照を(間接的に)それ自体に暗黙的に追加しているため、問題が発生しています。ブロックを提供する他の人が、イベントまたはそのインスタンス変数を生成するオブジェクトを参照している可能性は低いことに注意してください。これらは、ブロックが定義されているスコープ内にないためです。それらが(たとえばサブクラスに)ある場合、問題を文書化する以外にできることは何もありません。

問題の可能性を減らす1つの方法は、イベントを生成するオブジェクトをSPEventパラメーターのプロパティとして提供し、自分自身を参照する代わりにそれをブロックで使用することです。イベントを生成するオブジェクトがスコープ内にあるコンテキストでブロックを作成する必要がないため、とにかくそれを行う必要があります。

于 2010-10-20T10:43:00.753 に答える