¿Cómo puedo saber si un objeto tiene un observador de valor clave adjunto?

142

si le dice a un objeto c objetivo que elimine los observadores: para una ruta clave y esa ruta clave no se ha registrado, se quiebran los tristes. me gusta -

"No se puede eliminar un observador para la ruta de acceso clave" theKeyPath "porque no está registrado como observador".

¿Hay alguna manera de determinar si un objeto tiene un observador registrado, por lo que puedo hacer esto

if (object has observer){
  remove observer
}
else{
  go on my merry way
}
Aran Mulholland
fuente
Me metí en este escenario actualizando una aplicación anterior en iOS 8 donde se estaba desasignando un controlador de vista y lanzando la excepción "No se puede eliminar". Pensé que llamando addObserver:en viewWillAppear:y correspondientemente removeObserver:en viewWillDisappear:las llamadas fueron emparejados correctamente. Tengo que hacer una solución rápida, así que voy a implementar la solución try-catch y dejar un comentario para investigar más a fondo la causa.
bneely
Solo estoy lidiando con algo similar y veo que necesito analizar mi diseño más profundamente y ajustarlo para que no necesite quitar el observador nuevamente.
Bogdan
usar un valor bool como el sugerido en esta respuesta funcionó mejor para mí: stackoverflow.com/a/37641685/4833705
Lance Samaria

Respuestas:

315

Intente atrapar su llamada removeObserver

@try{
   [someObject removeObserver:someObserver forKeyPath:somePath];
}@catch(id anException){
   //do nothing, obviously it wasn't attached because an exception was thrown
}
Adán
fuente
12
1+ Buena respuesta, funcionó para mí y estoy de acuerdo con tu discurso antes de que fuera editado.
Robert
25
votó por una queja eliminada con la que probablemente estaría de acuerdo.
Ben Gotow
12
¿No hay alguna otra solución elegante? este toma al menos 2 ms por uso ... imagínelo en una celda de vista de tabla
João Nunes
19
Voto negativo porque omite decir que esto no es seguro para el código de producción y es probable que falle en cualquier momento. Elevar excepciones a través del código marco no es una opción en Cocoa.
Nikolai Ruhe
66
Cómo usar este código en swift 2.1. do {try self.playerItem? .removeObserver (self, forKeyPath: "status")} catch let error as NSError {print (error.localizedDescription)} obteniendo advertencia.
Vipulk617
37

La verdadera pregunta es por qué no sabes si lo estás observando o no.

Si está haciendo esto en la clase del objeto que se está observando, deténgase. Lo que sea que esté observando espera seguir observándolo. Si corta las notificaciones del observador sin su conocimiento, espere que las cosas se rompan; más específicamente, espere que el estado del observador se vuelva obsoleto ya que no recibe actualizaciones del objeto observado anteriormente.

Si está haciendo esto en la clase del objeto de observación, simplemente recuerde qué objetos está observando (o, si solo observa un objeto, ya sea que lo esté observando). Esto supone que la observación es dinámica y entre dos objetos que de otro modo no estarían relacionados; si el observador posee lo observado, simplemente agregue el observador después de crear o retener lo observado, y elimine el observador antes de liberar lo observado.

Agregar y eliminar un objeto como observador generalmente debería ocurrir en la clase del observador, y nunca en la del objeto observado.

Peter Hosey
fuente
14
Caso de uso: desea eliminar observadores en viewDidUnload y también en dealloc. Esto los elimina dos veces y arrojará la excepción si su viewController se descarga de una advertencia de memoria y luego se libera. ¿Cómo sugiere manejar este escenario?
bandejapaisa
2
@bandejapaisa: Más o menos lo que dije en mi respuesta: haga un seguimiento de si estoy observando y solo trate de dejar de observar si lo estoy haciendo.
Peter Hosey
41
No, esa no es una pregunta interesante. No debería tener que hacer un seguimiento de esto; debería poder simplemente anular el registro de todos los oyentes en dealloc, sin preocuparse por si golpeó la ruta del código donde se agregó o no. Debería funcionar como removeNbserver de NSNotificationCenter, a lo que no le importa si realmente tiene uno o no. Esta excepción es simplemente crear errores donde no existiría ninguno, lo cual es un mal diseño de API.
Glenn Maynard
1
@GlennMaynard: Como dije en la respuesta, “Si corta las notificaciones del observador sin su conocimiento, espere que las cosas se rompan; más específicamente, espere que el estado del observador se vuelva obsoleto ya que no recibe actualizaciones del objeto observado anteriormente ". Cada observador debe terminar su propia observación; Si no se hace esto, lo ideal es que sea muy visible.
Peter Hosey
3
Nada en la pregunta habla sobre la eliminación de los observadores de otros códigos.
Glenn Maynard
25

FWIW, [someObject observationInfo]parece ser nilsi someObjectno tiene observadores. Sin embargo, no confiaría en este comportamiento, ya que no lo he visto documentado. Además, no sé leer observationInfopara obtener observadores específicos.

ma11hew28
fuente
¿Sabes cómo puedo recuperar un observador específico? objectAtIndex:no produce el resultado deseado.)
Eimantas
1
@MattDiPasquale ¿Sabes cómo puedo leer Observaciones en el código? En las impresiones sale bien, pero es un puntero a anular. ¿Cómo debería leerlo?
neeraj
observaciónInfo es un método de depuración documentado en el documento de depuración de Xcode (algo con "magia" en el título). Puedes intentar buscarlo. Puedo decir que si necesita saber si alguien está observando su objeto, está haciendo algo mal. Reconsidera tu arquitectura y lógica. Lo aprendí de la manera difícil.)
Eimantas
Fuente:NSKeyValueObserving.h
nefarianblack
más 1 para un callejón sin salida cómico pero una respuesta útil
Will Von Ullrich el
4

La única forma de hacer esto es establecer una bandera cuando agrega un observador.

Leibowitzn
fuente
3
Cuando termines con BOOL en todas partes, será mejor que crees un objeto envoltorio KVO que maneje agregar el observador y eliminarlo. Puede garantizar que su observador solo se elimine una vez. Hemos utilizado un objeto como este, y funciona.
bandejapaisa
Gran idea si no siempre estás observando.
Andre Simon
4

Cuando agrega un observador a un objeto, puede agregarlo a algo NSMutableArrayasí:

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

Si desea no observar los objetos, puede hacer algo como:

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

Recuerde, si no observa un solo objeto, retírelo de la _observedObjectsmatriz:

- (void)removeObservedObject:(id)object {
    if ([_observedObjects containsObject:object]) {
        [_observedObjects removeObject:object];
    }
}
Oritm
fuente
1
Si esto sucede en un mundo de subprocesos múltiples, debe asegurarse de que su matriz sea ThreadSafe
shrutim
Mantiene una fuerte referencia de un objeto, lo que aumentaría el conteo de retención cada vez que se agrega un objeto en la lista y no se desasignará a menos que su referencia se elimine de la matriz. Preferiría usar NSHashTable/ NSMapTablepara mantener las referencias débiles.
atulkhatri
3

En mi opinión, esto funciona de manera similar al mecanismo retenerCount. No puede estar seguro de que en el momento actual tenga su observador. Incluso si marca : self.observationInfo : no puede estar seguro de que tendrá / no tendrá observadores en el futuro.

Como retenerCount . Quizás el método observaciónInfo no es exactamente ese tipo de inútil, pero solo lo uso con fines de depuración.

Como resultado, solo tiene que hacerlo como en la administración de memoria. Si agregó un observador, simplemente retírelo cuando no lo necesite. Como usar los métodos viewWillAppear / viewWillDisappear, etc. P.ej:

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

Y necesita algunas comprobaciones específicas: implemente su propia clase que maneja una variedad de observadores y úsela para sus comprobaciones.

quarezz
fuente
[self removeObserver:nil forKeyPath:@""]; necesita ir antes: [super viewWillDisappear:animated];
Joshua Hart
@JoshuaHart ¿por qué?
quarezz
Porque es un método de derribo (dealloc). Cuando anulas algún tipo de método de desmontaje, llamas super último. Como: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Joshua Hart
viewWillDisapear no es un método de desmontaje y no tiene conexión con dealloc. Si avanza hacia la pila de navegación, se llamará a viewWillDisapear , pero su vista permanecerá en la memoria. Veo a dónde vas con la lógica de configuración / desmontaje, pero hacerlo aquí no dará ningún beneficio real. Debería colocar la eliminación antes de super solo si tiene algo de lógica en la clase base, que podría entrar en conflicto con el observador actual.
quarezz
3

[someObject observationInfo]regresar nilsi no hay observador.

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

}
Anupama
fuente
De acuerdo con los documentos de Apple: observaciónInfo devuelve un puntero que identifica información sobre todos los observadores que están registrados con el receptor.
FredericK
Esto fue mejor dicho en la respuesta de @ mattdipasquale
Ben Leggiero
2

El objetivo del patrón de observador es permitir que una clase observada se "selle", para no saber o importar si se está observando. Está intentando explícitamente romper este patrón.

¿Por qué?

El problema que tienes es que estás asumiendo que te están observando cuando no lo estás. Este objeto no inició la observación. Si desea que su clase tenga control de este proceso, entonces debería considerar usar el centro de notificaciones. De esa manera, su clase tiene control total sobre cuándo se pueden observar los datos. Por lo tanto, no le importa quién está mirando.

adonoho
fuente
10
Él pregunta cómo el oyente puede descubrir si está escuchando algo, no cómo el objeto que se está observando puede descubrir si se está observando.
Glenn Maynard
1

No soy fanático de esa solución try catch, así que lo que hago la mayor parte del tiempo es crear un método de suscripción y cancelación de suscripción para una notificación específica dentro de esa clase. Por ejemplo, estos dos métodos suscriben o anulan la suscripción del objeto a la notificación global del teclado:

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

Dentro de esos métodos, uso una propiedad privada que se establece en verdadero o falso según el estado de suscripción de la siguiente manera:

@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
Sebastian Boldt
fuente
0

Además de la respuesta de Adam, me gustaría sugerir usar macro como esta

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

ejemplo de uso

- (void)dealloc {
    SafeRemoveObserver(someObject, self, somePath);
}
Wattson
fuente
1
¿Qué tan loco es que arroje una excepción? ¿Por qué simplemente no hace nada si nada se adjunta?
Aran Mulholland