Como posso saber se um objeto tem um observador de valor-chave anexado


142

se você disser a um objeto c objetivo que removeObservers: para um caminho-chave e esse caminho-chave não foi registrado, ele quebrará os tristes. gostar -

'Não é possível remover um observador para o caminho da chave "theKeyPath" porque ele não está registrado como observador.'

existe uma maneira de determinar se um objeto tem um observador registrado, para que eu possa fazer isso

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

Entrei nesse cenário atualizando um aplicativo antigo no iOS 8 em que um controlador de exibição estava sendo desalocado e lançando a exceção "Não é possível remover". Pensei que chamando addObserver:em viewWillAppear:e, correspondentemente, removeObserver:em viewWillDisappear:, as chamadas foram correctamente emparelhados. Eu tenho que fazer uma correção rápida para implementar a solução try-catch e deixar um comentário para investigar a causa.
bneely

Estou apenas lidando com algo semelhante e vejo que preciso examinar meu design mais profundamente e ajustá-lo para que não precise remover o observador novamente.
Bogdan

usar um valor bool como sugerido nesta resposta funcionou melhor para mim: stackoverflow.com/a/37641685/4833705
Lance Samaria

Respostas:


315

Experimente a sua chamada removeObserver

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

12
1+ Boa resposta, funcionou para mim e eu concordo com o seu discurso antes de ser editado.
Robert

25
votado para o discurso excluído com o qual eu provavelmente concordaria.
Ben Gotow

12
Aqui não há outra solução elegante? este leva pelo menos 2ms por uso ... imagine-o em uma tableviewcell
João Nunes

19
Voto negativo porque você está omitindo dizer que isso não é seguro para o código de produção e provavelmente falhará a qualquer momento. Gerar exceções através do código de estrutura não é uma opção no Cocoa.
Nikolai Ruhe

6
Como usar este código no swift 2.1. do {try self.playerItem? .removeObserver (self, forKeyPath: "status")} captura erro let como NSError {print (error.localizedDescription)} recebendo aviso.
Vipulk617

37

A verdadeira questão é por que você não sabe se está observando ou não.

Se você estiver fazendo isso na classe do objeto que está sendo observado, pare. O que quer que esteja observando, espera continuar observando. Se você interromper as notificações do observador sem o seu conhecimento, espere que as coisas quebrem; mais especificamente, espere que o estado do observador fique obsoleto, pois não recebe atualizações do objeto anteriormente observado.

Se você estiver fazendo isso na classe de objeto de observação, simplesmente lembre-se de quais objetos você está observando (ou, se você apenas observar um objeto, esteja observando). Isso pressupõe que a observação seja dinâmica e entre dois objetos não relacionados; se o observador possuir o observado, basta adicioná-lo após criar ou reter o observado e remover o observador antes de liberar o observado.

Adicionar e remover um objeto como observador geralmente deve ocorrer na classe do observador, e nunca na classe observada.


14
Caso de uso: você deseja remover os observadores no viewDidUnload e também no desalocação. Isso é removê-los duas vezes e lançará a exceção se o seu viewController for descarregado de um aviso de memória e depois liberado. Como você sugere lidar com esse cenário?
precisa saber é o seguinte

2
@bandejapaisa: Praticamente o que eu disse na minha resposta: acompanhe se estou observando e só tente parar de observar se estiver.
precisa saber é o seguinte

41
Não, essa não é uma pergunta interessante. Você não deveria ter que acompanhar isso; você poderá simplesmente cancelar o registro de todos os ouvintes em desassociação, sem se preocupar com o fato de ter atingido o caminho do código em que foi adicionado ou não. Ele deve funcionar como o removeObserver do NSNotificationCenter, que não se importa se você realmente tem um ou não. Essa exceção é simplesmente a criação de bugs onde nenhum deles existiria, o que é um design de API ruim.
perfil completo de Glenn Maynard

1
@ GlennMaynard: Como eu disse na resposta: “Se você interromper as notificações do observador sem o seu conhecimento, espere que as coisas quebrem; mais especificamente, espere que o estado do observador fique obsoleto, pois não recebe atualizações do objeto anteriormente observado. ” Todo observador deve terminar sua própria observação; a falha em fazer isso deve, idealmente, ser altamente visível.
11556 Peter Hosey

3
Nada na pergunta fala sobre remover os observadores de outros códigos.
perfil completo de Glenn Maynard

25

FWIW, [someObject observationInfo]parece ser nilse someObjectnão tiver nenhum observador. No entanto, eu não confiaria nesse comportamento, pois não o vi documentado. Além disso, não sei ler observationInfopara conseguir observadores específicos.


Você sabe como posso recuperar um observador específico? objectAtIndex:não deu resultado desejado).
Eimantas

1
@MattDiPasquale Você sabe como posso ler o ObservaçãoInfo no código? Nas impressões, está saindo bem, mas é um ponteiro para anular. Como devo ler?
22313 neeraj

ObservaçãoInfo é o método de depuração documentado no documento de depuração do Xcode (algo com "mágica" no título). Você pode tentar procurar. Eu posso dizer que se você precisa saber se alguém está observando seu objeto - você está fazendo algo errado. Repensar sua arquitetura e lógica. Aprendi isso da maneira mais difícil).
Eimantas

Fonte:NSKeyValueObserving.h
nefarianblack

mais 1 por um beco sem saída cômico, mas ainda assim uma resposta útil
Will Von Ullrich

4

A única maneira de fazer isso é definir um sinalizador quando você adiciona um observador.


3
Se você tiver BOOLs em todos os lugares, melhor ainda, criar um objeto wrapper KVO que controla a adição e a remoção do observador. Isso pode garantir que seu observador seja removido apenas uma vez. Nós usamos um objeto assim, e ele funciona.
precisa saber é o seguinte

ótima idéia se você nem sempre está observando.
Andre Simon

4

Quando você adiciona um observador a um objeto, pode adicioná-lo da NSMutableArrayseguinte maneira:

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

Se você deseja desobservar os objetos, pode fazer algo como:

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

Lembre-se, se você não observar um único objeto, remova-o da _observedObjectsmatriz:

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

1
Se isso acontecer em um mundo multi threaded, você precisa se certificar que sua matriz é threadsafe
shrutim

Você está mantendo uma referência forte de um objeto, o que aumentaria a contagem de retenção sempre que um objeto for adicionado à lista e não será desalocado, a menos que sua referência seja removida da matriz. Eu preferiria usar NSHashTable/ NSMapTablepara manter as referências fracas.
atulkhatri

3

Na minha opinião - isso funciona semelhante ao mecanismo reterCount. Você não pode ter certeza de que, no momento atual, você tem seu observador. Mesmo se você marcar: self.observationInfo - você não pode ter certeza de que terá ou não observadores no futuro.

Como reterCount . Talvez o método observaçãoInfo não seja exatamente esse tipo de inútil, mas eu o uso apenas para fins de depuração.

Então, como resultado - você só precisa fazer isso como no gerenciamento de memória. Se você adicionou um observador - basta removê-lo quando não precisar. Como usar os métodos viewWillAppear / viewWillDisappear etc. Por exemplo:

-(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:@""];
}

E você precisa de algumas verificações específicas - implemente sua própria classe que lida com uma série de observadores e use-a para suas verificações.


[self removeObserver:nil forKeyPath:@""]; precisa ir antes: [super viewWillDisappear:animated];
Joshua Hart

@JoshuaHart why?
quarezz

Porque é um método de desmontagem (dealloc). Quando você substitui algum tipo de método de desmontagem, você chama super last. Como: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart

O viewWillDisapear não é um método de desdobramento e não tem conexão com o dealloc. Se você avançar para a pilha de navegação, o viewWillDisapear será chamado, mas sua visualização permanecerá na memória. Vejo para onde você está indo com a lógica da configuração / desmontagem, mas fazê-lo aqui não trará nenhum benefício real. Você gostaria de colocar a remoção antes de super somente se tiver alguma lógica na classe base, que possa entrar em conflito com o observador atual.
quarezz

3

[someObject observationInfo]retorne nilse não houver observador.

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

}

De acordo com os documentos da Apple: ObservaçãoInfo retorna um ponteiro que identifica informações sobre todos os observadores registrados no receptor.
FredericK

Este foi melhor-disse em resposta de @ mattdipasquale
Ben leggiero

2

O objetivo principal do padrão observador é permitir que uma classe observada seja "selada" - não saber ou se importar se está sendo observada. Você está tentando explicitamente quebrar esse padrão.

Por quê?

O problema que você está tendo é que você está assumindo que está sendo observado quando não está. Este objeto não iniciou a observação. Se você deseja que sua turma tenha controle sobre esse processo, considere usar o centro de notificação. Dessa forma, sua classe tem controle total sobre quando os dados podem ser observados. Portanto, não importa quem está assistindo.


10
Ele está perguntando como o ouvinte pode descobrir se está ouvindo algo, não como o objeto sendo observado pode descobrir se está sendo observado.
perfil completo de Glenn Maynard

1

Como não sou fã dessa solução try catch, o que faço na maioria das vezes é que crio um método de inscrição e cancelamento de inscrição para uma notificação específica dentro dessa classe. Por exemplo, esses dois métodos assinam ou cancelam a inscrição do objeto na notificação global do teclado:

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

Dentro desses métodos, uso uma propriedade privada configurada como true ou false, dependendo do estado da assinatura, da seguinte maneira:

@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

Além da resposta de Adam, gostaria de sugerir o uso de macro como esta

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

exemplo de uso

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

1
Quão louco é isso lançar uma exceção? Por que simplesmente não faz nada se nada está ligado?
Aran Mulholland
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.