27

オブジェクトがCocoa/Objective-Cで適切に解放/保持されていることを確認するために、たとえばOCUnitを使用して単体テストをどのように記述しますか?

これを行うための単純な方法は、の値を確認することですretainCountが、もちろん、を使用しないでくださいretainCount。オブジェクトの参照に値が割り当てられているかどうかを簡単に確認して、オブジェクトnilが解放されたことを示すことができますか?また、オブジェクトが実際に割り当て解除されるタイミングについて、どのような保証がありますか?

私はおそらくこれを広範囲に使用するので、ほんの数行のコードの簡潔な解決策を望んでいます。実際には2つの答えがあります。1つは自動解放プールを使用し、もう1つは使用しません。

明確にするために、私は自分が作成するすべてのオブジェクトを包括的にテストする方法を探していません。メモリ管理は言うまでもなく、動作を包括的に単体テストすることは不可能です。ただし、少なくとも、回帰テストのために解放されたオブジェクトの動作をチェックすることは素晴らしいことです(そして、同じメモリ関連のバグが2回発生しないことを確認してください)。

回答について

自動参照カウントで提供される弱ポインタ​​ーはXCodeの製品版では使用できないという警告を考慮して、 BJ Homer回答を受け入れました。これは、私が考えていたことを達成するための最も簡単で簡潔な方法であることがわかったためです。 〜4.2?)2011年7月23日現在。私もそれを知って感動しました

ARCはファイルごとに有効にできます。プロジェクト全体で使用する必要はありません。ARCを使用して単体テストをコンパイルし、メインプロジェクトを手動の保持リリースのままにしておくことができますが、このテストは引き続き機能します。

そうは言っても、Objective-Cでのユニットテストのメモリ管理に関連する潜在的な問題をより詳細に調査するには、PeterHosey詳細な対応を強くお勧めします。

4

3 に答える 3

17

オブジェクトの参照に値が割り当てられているかどうかを簡単に確認して、オブジェクトnilが解放されたことを示すことができますか?

いいえ、releaseオブジェクトへのメッセージの送信nilと変数への割り当ては、2つの異なる無関係なものであるためです。

最も近いのは、強い/保持またはコピーするプロパティに何かを割り当てると、アクセサメッセージに変換され、プロパティの以前の値が解放されることです(これはセッターによって行われます)。それでも、KVOを使用してプロパティの値を監視することは、オブジェクトがいつリリースされるかを知ることを意味するわけではありません。特に、所有オブジェクトの割り当てが解除された場合、所有オブジェクトreleaseに直接送信しても通知は届きません。また、コンソールに警告メッセージが表示され(所有しているオブジェクトが監視中に死亡したため)、単体テストからのノイズの多い警告メッセージは必要ありません。さらに、これを実行するには、すべてのオブジェクトのすべてのプロパティを具体的に監視する必要があります。1つを見逃すと、バグを見逃す可能性があります。

オブジェクトへのreleaseメッセージは、そのオブジェクトを指す変数には影響しません。割り当て解除も行いません。

これはARCでわずかに変更されます。参照さnilれるオブジェクトがなくなると、弱い参照変数が自動的に割り当てられます。ただし、定義上、変数を強く参照することはあまり役に立ちません。オブジェクトへの強い参照がある場合、強い参照があるため、オブジェクトは消えませ(まあ、そうすべきではありません)。それを生かしておく(すべき)でしょう。オブジェクトが死ぬ前に死ぬことは、あなたが探している問題の1つであり、ツールとして使用したいものではありません。

理論的には、作成するすべてのオブジェクトへの弱参照を作成できますが、コード内で手動でその変数を作成して、すべてのオブジェクトを具体的に参照する必要があります。ご想像のとおり、ものすごい苦痛と間違いなく物を見逃します。

また、オブジェクトが実際にリリースされるタイミングについて、どのような保証がありますか?

オブジェクトはメッセージを送信することで解放されるreleaseため、オブジェクトはそのメッセージを受信すると解放されます。

おそらくあなたは「割り当て解除」を意味しました。リリースすると、そのポイントに近づくだけです。オブジェクトは何度もリリースされる可能性がありますが、各リリースが以前の保持と単にバランスをとっていれば、オブジェクトの前に長い寿命があります。

オブジェクトは、最後に解放されたときに割り当てが解除されます。これはすぐに起こります。悪名高いretainCount人は、書き込もうとした多くの賢い人while ([obj retainCount] > 0) [obj release];が知っているように、0まで下がることさえありません。

実際には2つの答えがあります。1つは自動解放プールを使用し、もう1つは使用しません。

自動解放プールを使用するソリューションは、自動解放されたオブジェクトに対してのみ機能します。定義上、自動解放されないオブジェクトはプールに入りません。特定のオブジェクト(特に何千ものオブジェクトを作成するオブジェクト)を自動リリースしないことは完全に有効であり、場合によっては望ましいことです。さらに、プールを調べて何が入っているのか、何が入っていないのかを確認したり、各オブジェクトを突いて死んでいるかどうかを確認したりすることはできません。

オブジェクトがCocoa/Objective-Cで適切に解放/保持されていることを確認するために、たとえばOCUnitを使用して単体テストをどのように記述しますか?

最善の方法は、に設定NSZombieEnabledYESて、setUpの以前の値を復元することtearDownです。これは、過剰リリース/不足保持をキャッチしますが、いかなる種類のリークもキャッチしません。

メモリ管理を徹底的にテストする単体テストを作成できたとしても、テスト可能なコード(モデルオブジェクト、場合によっては特定のコントローラー)しかテストできないため、不完全です。ビューコード、ペン先での参照、特定のオプション(「閉じたときにリリース」が頭に浮かぶ)などが原因で、アプリケーションにリークやクラッシュが発生する可能性があります。

アプリケーションにメモリバグがないことを確認するために作成できるアプリケーション外テストはありません。

とは言うものの、あなたが想像しているようなテストは、それが自己完結型で自動である場合、すべてをテストできなくても、かなりクールです。ですから、私が間違っていて、方法があることを願っています。

于 2011-07-05T09:56:05.253 に答える
14

新しく導入された自動参照カウント(Xcodeの製品版ではまだ利用できませんが、ここに記載されています)を使用できる場合は、弱いポインターを使用して、何かが過剰に保持されているかどうかをテストできます。

- (void)testMemory {
    __weak id testingPointer = nil;
    id someObject = // some object with a 'foo' property

    @autoreleasepool {
        // Point the weak pointer to the thing we expect to be dealloc'd
        // when we're done.
        id theFoo = [someObject theFoo];
        testingPointer = theFoo;

        [someObject setTheFoo:somethingElse];

        // At this point, we still have a reference to 'theFoo',
        // so 'testingPointer' is still valid. We need to nil it out.
        STAssertNotNil(testingPointer, @"This will never happen, since we're still holding it.")

        theFoo = nil;
    }


    // Now the last strong reference to 'theFoo' should be gone, so 'testingPointer' will revert to nil
    STAssertNil(testingPointer, @"Something didn't release %@ when it should have", testingPointer);
}

言語セマンティクスがこのように変更されたため、これはARCで機能することに注意してください。

保持可能なオブジェクトポインタは、nullポインタまたは有効なオブジェクトへのポインタのいずれかです。

したがって、ポインタをnilに設定するという行為は、それが指すオブジェクトを解放することが保証されており、(ARCの下では)ポインタを削除せずにオブジェクトを解放する方法はありません。

注意すべき点の1つは、ARCはファイルごとに有効にできるということです。プロジェクト全体で使用する必要はありません。ARCを使用して単体テストをコンパイルし、メインプロジェクトを手動の保持リリースのままにしておくことができますが、このテストは引き続き機能します。

上記は過剰リリースを検出しませんが、NSZombieEnabledとにかくそれをキャッチするのはかなり簡単です。

ARCが単にオプションではない場合は、MikeAshと同様のことができる可能性がありますMAZeroingWeakRef。私はあまり使用していませんが、下位互換性のある方法で__weakポインターと同様の機能を提供しているようです。

于 2011-07-05T16:07:23.617 に答える
1

これはおそらくあなたが探しているものではありませんが、思考実験として、これがあなたが望むものに近い何かをするのではないかと思いました:あなたがテストしたい特定のオブジェクトの保持/解放動作を追跡するメカニズムを作成した場合はどうなりますか?次のように作業します。

  1. NSObjectdeallocのオーバーライドを作成します
  2. を作成し、CFMutableSetRef何もしないようにカスタムの保持/解放関数を設定します
  3. 次のような単体テストルーチンを作成しますregisterForRRTracking: (id) object
  4. そのようなユニットテストルーチンclearRRTrackingReportingLeaks: (BOOL) reportを作成すると、その時点でセット内のすべてのオブジェクトが報告されます。
  5. [tracker clearRRTrackignReportingLeaks: NO];ユニットテストの開始時に電話する
  6. 追跡するすべてのオブジェクトの単体テストでregisterメソッドを呼び出すと、deallocで自動的に削除されます。
  7. テストの最後にを呼び出すと、[tracker clearRRTrackingReportingLeaks: YES];適切に廃棄されなかったすべてのオブジェクトが一覧表示されます。

オーバーライドしてすべてNSObject allocを追跡することもできますが、セットが大きくなりすぎると思います(!!!)。

さらに良いのはCFMutableSetRef、を別のプロセスに配置して、プログラムの実行時のメモリフットプリントに過度の影響を与えないようにすることです。ただし、プロセス間通信の複雑さと実行時のヒットが追加されます。プライベートヒープ(またはゾーン-それらはまだ存在しますか?)を使用して、それをより少ない程度で分離することができます。

于 2011-07-15T17:18:15.087 に答える