44

I was wondering what you should set the Context pointer in KVO when you are observing a property. I'm just starting to use KVO and I haven't gleaned too much from the documentation. I see on this page: http://www.jakeri.net/2009/12/custom-callout-bubble-in-mkmapview-final-solution/ the author does this:

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

And then in the callback, does this:

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

NSString *action = (NSString*)context;


if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){

I'm assuming in this scenario, the author just creates a string to be identified later in the callback.

Then in iOS 5 Pushing the Limits book, I see he does this:

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

callback:

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

I was wondering if there is a standard or best practices to pass into the context pointer?

4

3 に答える 3

102

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

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

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

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

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

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

使用しないように特に注意するパターンの 1つは、質問に表示されたものです。

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

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

contextは であると宣言されてvoid*います。つまり、それが何であるかについて保証できるのはそれだけです。にキャストすることでNSString*、潜在的な悪の大きな箱を開けています。パラメータにを使用しない登録を誰かが行った場合、オブジェクト以外の値を に渡すと、このアプローチはクラッシュします。ポインターの等価性 (または代替または等価性) は、値で使用できる唯一の安全なチェックです。NSString*contextisEqualToString:intptr_tuintptr_tcontext

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

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

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

#define MyKVOContext(A) static void * const A = (void*)&A;
于 2013-01-04T17:59:28.173 に答える
19

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

ファイルの上部近くClassName.mに次の行があります

static char ClassNameKVOContext = 0;

(のインスタンス)のaspectプロパティを観察し始めると、targetObjectTargetClass

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

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

観察aspectが終わっtargetObjectたら

[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
        }
    }
}
于 2012-10-17T04:35:18.500 に答える