web-dev-qa-db-ja.com

addObserver(KVO)のコンテキストパラメータのベストプラクティス

プロパティを監視しているときに、KVOでコンテキストポインターを何に設定すればよいかと思いました。 KVOを使い始めたばかりで、ドキュメントからあまり情報を収集していません。私はこのページを見ます: http://www.jakeri.net/2009/12/custom-callout-bubble-in-mkmapview-final-solution/ 著者はこれを行います:

[annView addObserver:self
forKeyPath:@"selected"
options:NSKeyValueObservingOptionNew
context:GMAP_ANNOTATION_SELECTED];

そして、コールバックでこれを行います:

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context{

NSString *action = (NSString*)context;


if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){

このシナリオでは、作成者はコールバックで後で識別される文字列を作成するだけだと想定しています。

次に、iOS 5 Pushing the Limitsの本で、彼がこれを行っているのがわかります。

[self.target addObserf:self forKeyPath:self.property options:0 context:(__bridge void *)self];

折り返し電話:

if ((__bridge id)context == self) {
}
else {
   [super observeValueForKeyPath .......];
}

コンテキストポインターに渡す標準またはベストプラクティスがあるかどうか疑問に思っていましたか?

41
Crystal

重要なことは、(一般的に言えば)何か(何もないのではなく)を使用することと、使用するものは何でも一意およびそれを使用することにプライベートであること)です。

ここでの主な落とし穴は、クラスの1つにオブザベーションがあり、誰かがクラスをサブクラス化し、同じオブザベーションオブジェクトと同じkeyPathの別のオブザベーションを追加したときに発生します。元のobserveValueForKeyPath:...の実装がkeyPath、または観測されたobject、あるいはその両方をチェックしただけの場合、それがであることを知るには不十分かもしれませんyour観測がコールバックされました。値が一意でプライベートであるcontextを使用すると、observeValueForKeyPath:...への特定の呼び出しが期待どおりの呼び出しであることをより確実にすることができます。

たとえば、didChange通知のみを登録したが、サブクラスがNSKeyValueObservingOptionPriorオプションを使用して同じオブジェクトとkeyPathを登録した場合、これは問題になります。 contextを使用してobserveValueForKeyPath:...への呼び出しをフィルタリングしない場合(または変更辞書を確認する場合)、ハンドラーは複数回実行されますが、1回しか実行されないと予想されていました。これがどのように問題を引き起こすかを想像するのは難しくありません。

私が使用するパターンは次のとおりです。

static void * const MyClassKVOContext = (void*)&MyClassKVOContext;

このポインターは独自の場所を指し、その場所は一意です(他の静的変数またはグローバル変数はこのアドレスを持つことができず、ヒープまたはスタックに割り当てられたオブジェクトがこのアドレスを持つことはできません-確かに強力ではありませんabsolute、ギャランティ)、リンカに感謝します。 constは、ポインターの値を変更するコードを記述しようとした場合にコンパイラーが警告するようにするためのものです。最後に、staticはそれをこのファイルに対して非公開にします。このファイルの外部のファイルは、そのファイルへの参照を取得できます(ここでも、衝突を回避する可能性が高くなります)。

againstに対して特に注意するパターンの1つは、質問に出てきたパターンです。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    NSString *action = (NSString*)context;
    if([action isEqualToString:GMAP_ANNOTATION_SELECTED]) {

contextvoid*として宣言されています。これは、それが何であるかについて保証できるすべてのことです。それをNSString*にキャストすると、潜在的な問題の大きな箱が開かれます。他の誰かがしないではない登録を持っている場合、contextパラメータにNSString*を使用すると、このアプローチは非オブジェクト値をisEqualToString:に渡すとクラッシュします。ポインタの等価性(またはintptr_tまたはuintptr_tの等価性)は、context値で使用できる唯一の安全なチェックです。

selfcontextとして使用することは一般的なアプローチです。他のオブジェクト(サブクラスは言うまでもありません)はselfの値にアクセスでき、それをcontextとして使用する可能性があるため(あいまいさを引き起こす)、何もないよりはましですが、一意化とプライバシーが大幅に低下します。 、私が上記で提案したアプローチとは異なり。

また、ここで落とし穴を引き起こす可能性があるのはjustサブクラスではないことも覚えておいてください。これは間違いなくまれなパターンですが、別のオブジェクトが新しいKVO観測用にオブジェクトを登録するのを妨げるものは何もありません。

読みやすくするために、次のようなプリプロセッサマクロでこれをラップすることもできます。

#define MyKVOContext(A) static void * const A = (void*)&A;
98
ipmcc

this Gist が示すように、KVOコンテキストは静的変数へのポインターである必要があります。通常、私は次のことをしています。

ファイルの先頭近くにClassName.mがあります

static char ClassNameKVOContext = 0;

aspecttargetObjectのインスタンス)のTargetClassプロパティを監視し始めると、

[targetObject addObserver:self
               forKeyPath:PFXKeyTargetClassAspect
                  options://...
                  context:&ClassNameKVOContext];

ここで、PFXKeyTargetClassAspectはNSString *TargetClass.mと等しくなるように定義され、@"aspect"externと宣言されたTargetClass.hです。 (もちろん、PFXはプロジェクトで使用している接頭辞のプレースホルダーにすぎません。)これにより、オートコンプリートの利点が得られ、タイプミスから保護されます。

aspecttargetObjectの監視を終了すると、

[targetObject removeObserver:self
                  forKeyPath:PFXKeyTargetClassAspect
                     context:&ClassNameKVOContext];

-observeValueForKeyPath:ofObject:change:context:の実装でインデントが多すぎないようにするために、

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context != &ClassNameKVOContext) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }

    if ([object isEqual:targetObject]) {
        if ([keyPath isEqualToString:PFXKeyTargetClassAspect]) {
            //targetObject has changed the value for the key @"aspect".
            //do something about it
        }
    }
}
18
Nate Chandler

私はそれをAppleのドキュメントサイスとして実装する方が良いと思います:

クラス内で一意に名前が付けられた静的変数のアドレスは、適切なコンテキストになります。

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;

ドキュメント を参照してください。

1