オブジェクトにキー値オブザーバーがアタッチされているかどうかを確認する方法


142

オブジェクティブcオブジェクトにremoveObservers:を指示すると、キーパスがそのキーパスに登録されていない場合、悲しいことが起こります。お気に入り -

'キーパス「theKeyPath」のオブザーバーは、オブザーバーとして登録されていないため、削除できません。'

オブジェクトにオブザーバーが登録されているかどうかを確認する方法があるので、これを行うことができます

if (object has observer){
  remove observer
}
else{
  go on my merry way
}

このシナリオでは、ビューコントローラーの割り当てが解除されていて、「削除できません」例外がスローされていたiOS 8の古いアプリを更新しています。で呼び出しaddObserver:viewWillAppear:それに対応removeObserver:viewWillDisappear:て呼び出しが正しくペアリングされていると思いました。すぐに修正する必要があるので、try-catchソリューションを実装し、コメントを残して原因をさらに調査します。
15年

私は同じようなものを扱っているだけで、オブザーバーを再び削除する必要がないように、デザインをより深く調べて調整する必要があると思います。
Bogdan 2016年

この回答で提案されているようなブール値を使用すると、私にとって最も効果的でした:stackoverflow.com/a/37641685/4833705
Lance Samaria

回答:


315

removeObserver呼び出しの周りに試行キャッチを配置します

@try{
   [someObject removeObserver:someObserver forKeyPath:somePath];
}@catch(id anException){
   //do nothing, obviously it wasn't attached because an exception was thrown
}

12
1+良い答え、私のために働いた、そしてそれが編集される前にあなたの怒りに同意する。
ロバート

25
私が同意する可能性が最も高い削除された暴言に賛成。
Ben Gotow

12
ここに他のエレガントな解決策はありませんか?これは... tableviewcellでそれを想像し、使用あたり少なくとも2ミリ秒かかります
ジョアン・ヌネス

19
これは、運用コードにとって安全ではなく、いつでも失敗する可能性が高いと言うのを省略しているため、反対票を投じました。フレームワークコードを通じて例外を発生させることは、Cocoaのオプションではありません。
Nikolai Ruhe 2013年

6
このコードをSwift 2.1で使用する方法。do {try self.playerItem?.removeObserver(self、forKeyPath: "status")}キャッチエラーをNSErrorとしてキャッチ{print(error.localizedDescription)}警告を取得。
Vipulk617

37

本当の問題は、それを観察しているかどうかわからない理由です。

監視対象のオブジェクトのクラスでこれを行っている場合は、停止します。それを観察しているものは何でもそれを観察し続けることを期待しています。知らないうちにオブザーバーの通知を遮断した場合は、事態が悪化することを期待してください。より具体的には、以前に監視されたオブジェクトから更新を受信しないため、監視者の状態が古くなることを期待します。

監視しているオブジェクトのクラスでこれを実行している場合は、監視しているオブジェクトを覚えておいてください(または、1つのオブジェクトしか監視していない場合は、それを監視しているかどうか)。これは、観測が動的で、2つの他の無関係なオブジェクト間であると想定しています。オブザーバーがオブザーバーを所有している場合は、オブザーバーを作成または保持した後でオブザーバーを追加し、オブザーバーを解放する前にオブザーバーを削除します。

オブザーバーとしてのオブジェクトの追加と削除は、通常、オブザーバーのクラスで行われるべきであり、オブザーブされたオブジェクトのクラスでは決して行われません。


14
使用例:viewDidUnloadおよびdeallocでオブザーバーを削除します。これはそれらを2回削除しており、viewControllerがメモリ警告からアンロードされ、その後解放された場合に例外をスローします。このシナリオをどのように処理することをお勧めしますか?
bandejapaisa

2
@bandejapaisa:私が私の答えで言ったことのほとんど:私が観察しているかどうかを追跡し、観察している場合にのみ観察を停止しようとします。
Peter Hosey 2012年

41
いいえ、それは興味深い質問ではありません。これを追跡する必要はありません。追加されたコードパスにたまたま到達したかどうかを気にせずに、deallocですべてのリスナーを登録解除するだけで済みます。NSNotificationCenterのremoveObserverのように機能するはずです。実際に持っているかどうかは関係ありません。この例外は、他に何も存在しないバグを作成するだけであり、これはAPI設計が悪いことです。
Glenn Maynard

1
@GlennMaynard:私が答えで言ったように、「知らないうちにオブザーバーの通知を遮断した場合、物事が壊れることを期待してください。具体的には、以前に監視されていたオブジェクトからの更新を受信しないため、監視者の状態が古くなることを期待します。」すべてのオブザーバーは独自の観察を終了する必要があります。これを怠ると、理想的には目立つようになります。
Peter Hosey

3
問題の中で、他のコードのオブザーバーを削除することについては触れていません。
Glenn Maynard

25

FWIW、オブザーバーがいない場合の[someObject observationInfo]ようです。文書化されていないため、この動作は信頼できません。また、特定のオブザーバーを取得するための読み方もわかりません。nilsomeObjectobservationInfo


特定のオブザーバーを取得する方法を知っていますか?objectAtIndex:望ましい結果が得られません。)
Eimantas 2012年

1
@MattDiPasqualeコードでobservationInfoを読み取る方法を知っていますか?版画ではそれはうまく出ていますが、それは無効へのポインタです。どうすれば読むことができますか?
neeraj 2013年

ObservationInfoは、Xcodeのデバッグ用紙に記載されているデバッグメソッドです(タイトルに「マジック」が付いているもの)。あなたはそれを調べてみることができます。あなたが誰かがあなたのオブジェクトを観察しているかどうかを知る必要があるなら、あなたは何か間違ったことをしていると私は言うことができます。アーキテクチャとロジックを再考します。難しい方法でそれを学びました。)
Eimantas 14

出典:NSKeyValueObserving.h
nefarianblack

コミカルな行き止まりであるが、まだいくらか役立つ答えにプラス1
Will Von Ullrich

4

これを行う唯一の方法は、オブザーバーを追加するときにフラグを設定することです。


3
どこにでもBOOLができますが、オブザーバーの追加と削除を処理するKVOラッパーオブジェクトを作成することをお勧めします。オブザーバーが一度だけ削除されることを保証できます。このようなオブジェクトを使用しましたが、機能します。
bandejapaisa 2012年

あなたがいつも観察しているわけではないなら素晴らしいアイデアです。
Andre Simon、

4

オブジェクトにオブザーバーを追加すると、次のNSMutableArrayように追加できます。

- (void)addObservedObject:(id)object {
    if (![_observedObjects containsObject:object]) {
        [_observedObjects addObject:object];
    }
}

オブジェクトの監視を解除したい場合は、次のようにします。

for (id object in _observedObjects) {
    if ([object isKindOfClass:[MyClass class]]) {
        MyClass *myObject = (MyClass *)object;
        [self unobserveMethod:myObject];
    }
}
[_observedObjects removeAllObjects];

1つのオブジェクトを監視しない場合は、_observedObjects配列から削除してください。

- (void)removeObservedObject:(id)object {
    if ([_observedObjects containsObject:object]) {
        [_observedObjects removeObject:object];
    }
}

1
マルチスレッドの世界でこれが発生する場合は、アレイがThreadSafeであることを確認する必要があります
shrutim

オブジェクトの強い参照を保持しているため、オブジェクトがリストに追加されるたびに保持カウントが増加し、その参照が配列から削除されない限り、割り当てが解除されません。弱い参照を保持するためにNSHashTable/ NSMapTableを使用したいと思います。
atulkhatri

3

私の意見では、これは、retainCountメカニズムと同様に機能します。現時点では、オブザーバーがいることを確信できません。チェックした場合でも、self.observationInfo-将来オブザーバーが存在するかどうかを確実に知ることはできません。

同様retainCount。もしかすると、observationInfoメソッドは、そのような種類の役に立たないわけではありませんが、デバッグ目的でのみ使用します。

したがって、結果として、メモリ管理と同じように実行する必要があります。オブザーバーを追加した場合-不要な場合は削除してください。viewWillAppear / viewWillDisappearなどのメソッドを使用するように。例えば:

-(void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self addObserver:nil forKeyPath:@"" options:NSKeyValueObservingOptionNew context:nil];
}

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self removeObserver:nil forKeyPath:@""];
}

そして、いくつかの特定のチェックが必要です-オブザーバーの配列を処理する独自のクラスを実装し、チェックに使用します。


[self removeObserver:nil forKeyPath:@""]; 前に移動する必要があります: [super viewWillDisappear:animated];
ジョシュアハート

@JoshuaHartなんで?
quarezz 2017

分解メソッド(dealloc)だからです。ある種のティアダウンメソッドをオーバーライドする場合、super lastを呼び出します。例: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
ジョシュアハート

viewWillDisapearは分解メソッドではなく、deallocとは関係ありません。ナビゲーションスタックに進むと、viewWillDisapearが呼び出されますが、ビューはメモリに残ります。あなたはセットアップ/ティアダウンのロジックでどこへ行くのか分かりますが、ここでそれをしても、実際の利益はありません。基本クラスにロジックがあり、現在のオブザーバーと競合する可能性がある場合にのみ、スーパーの前に削除を配置する必要があります。
quarezz 2017

3

[someObject observationInfo]nilオブザーバーがいない場合は戻ります。

if ([tableMessage observationInfo] == nil)
{
   NSLog(@"add your observer");
}
else
{
  NSLog(@"remove your observer");

}

Apple docsによると、observationInfoは、レシーバーに登録されているすべてのオブザーバーに関する情報を識別するポインターを返します。
FredericK


2

オブザーバーパターンの要点は、監視対象のクラスを「封印」できるようにすることです。つまり、監視対象かどうかを知らないか、気にする必要はありません。あなたは明示的にこのパターンを破ろうとしています。

どうして?

あなたが抱えている問題は、観察されていないときに観察されていると想定していることです。このオブジェクトは観測を開始しませんでした。クラスがこのプロセスを制御できるようにする場合は、通知センターの使用を検討する必要があります。これにより、データを監視できるタイミングをクラスが完全に制御できるようになります。したがって、誰が見ているかは関係ありません。


10
彼は、リスナーが何かを聞いているかどうかをどのように知ることができるかを尋ねています。観察されているオブジェクトがそれが観察されているかどうかを知る方法ではありません。
Glenn Maynard

1

私はそのtry catchソリューションのファンではないため、ほとんどの場合、そのクラス内の特定の通知に対してサブスクライブおよびサブスクライブ解除メソッドを作成します。たとえば、次の2つのメソッドは、オブジェクトをグローバルキーボード通知にサブスクライブまたはサブスクライブ解除します。

@interface ObjectA : NSObject
-(void)subscribeToKeyboardNotifications;
-(void)unsubscribeToKeyboardNotifications;
@end

これらのメソッド内では、サブスクリプションの状態に応じてtrueまたはfalseに設定されたプライベートプロパティを使用します。

@interface ObjectA()
@property (nonatomic,assign) BOOL subscribedToKeyboardNotification
@end

@implementation

-(void)subscribeToKeyboardNotifications {
    if (!self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardShow:) name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardHide:) name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = YES;
    }
}

-(void)unsubscribeToKeyboardNotifications {
    if (self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = NO;
    }
}
@end

0

アダムの答えに加えて、私はこのようなマクロを使用することを提案したいと思います

#define SafeRemoveObserver(sender, observer, keyPath) \
@try{\
   [sender removeObserver:observer forKeyPath:keyPath];\
}@catch(id anException){\
}

使用例

- (void)dealloc {
    SafeRemoveObserver(someObject, self, somePath);
}

1
それが例外をスローするのはどれほどクレイジーですか?何もアタッチされていないのに、何もしないのはなぜですか?
Aran Mulholland 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.