2011-07-04 10 views
14

En mi subclase UIScrollView, estoy observando los cambios del marco:¿Qué pasa con este observeValueForKeyPath: ofObject: change: context: implementation?

[self addObserver:self forKeyPath:@"frame" options:0 context:NULL]; 

Mi observeValueForKeyPath:ofObject:change:context: aplicación es el siguiente:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 
    if (object == self && [keyPath isEqualToString:@"frame"]) { 
     [self adjustSizeAndScale]; 
    } 
    if ([UIScrollView instancesRespondToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) { 
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; // Exception 
    } 
} 

pero me da una excepción con este código:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<WLImageScrollView: 0x733a440; baseClass = UIScrollView; frame = (0 0; 320 416); clipsToBounds = YES; layer = <CALayer: 0x7346500>; contentOffset: {0, 0}>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. 
Key path: frame 
Observed object: <WLImageScrollView: 0x733a440; baseClass = UIScrollView; frame = (0 0; 320 416); clipsToBounds = YES; layer = <CALayer: 0x7346500>; contentOffset: {0, 0}> 
Change: { 
    kind = 1; 
} 
Context: 0x0' 

¿Significa que UIScrollView implementa observeValueForKeyPath:ofObject:change:context: pero arroja la excepción anterior?

Si es así, ¿cómo puedo implementar correctamente observeValueForKeyPath:ofObject:change:context: para que yo pueda manejar mis cambios interesados ​​y darle a la superclase la oportunidad de manejar los cambios que le interesan?

Respuesta

12

Editar: BJ La respuesta de Homer es probablemente una mejor aproximación que tomar aquí; ¡Olvidé todo sobre el parámetro de contexto!

A pesar de que llamar a la aplicación Super es by-the-libro, llamando seems likeobserveValueForKeyPath:ofObject:change:context: en UIKit clases que en realidad no observan los campos en cuestión, se emite una excepción NSInternalInconsistency (no el NSInvalidArgumentException que se obtendría con un selector no reconocido) La cadena clave en la excepción que me sugiere esto es "recibida pero no procesada".

Por lo que yo sé, no hay una forma bien documentada de averiguar si un objeto observa otro objeto en una ruta clave determinada. Puede haber formas parcialmente documentadas, como la propiedad -observationInfo, que se dice que lleva información sobre los observadores de un objeto, pero usted está solo allí, es un void *.

Así como lo veo, tienes dos opciones: o bien no llame a la aplicación super o utilizar un bloque @try/@catch/@finally hacer caso omiso de ese tipo específico de NSInternalInconsistencyException. La segunda opción es probablemente más a prueba de futuro, pero tengo la corazonada de que algún trabajo de detective podría obtener resultados más satisfactorios a través de la primera opción.

+1

El artículo se ha vinculado a está hablando de otra cosa: la superclase no implementa observeValueForKeyPath: ofObject: el cambio: contexto :. Pero aquí, como puede ver en el código, UIScrollView lo implementa, de lo contrario, no se llamará al supermétodo. – an0

+0

Incluso si implementa el método, podría tener un código que detecte claves no observadas y arroje excepciones para ellas; por lo tanto, la excepción 'NSInternalInconsistency 'recibida pero no administrada', que es diferente del selector' NSInvalidArgumentException' 'no reconocido enviado a la instancia ". –

+1

Eso es lo que estoy preguntando. Entonces, la implementación predeterminada de observeValueForKeyPath: ofObject: change: context: ¿en NSObject lanza una excepción?Si es así, no hay nada que podamos hacer para detectar si la superclase desea ser notificada de los cambios de valores clave, ¿no es así? – an0

16

Debe agregar un valor de context cuando agrega el observador. En su método -observeValueForKeyPath, verifique el parámetro de contexto. Si no es el contexto que aprobó cuando agregó el observador, entonces sabe que este mensaje no está destinado a su subclase y puede pasarlo a la superclase. Si es el mismo valor, entonces usted sabe que es para usted y no debe pasarlo a super.

De esta manera:

static void *myContextPointer; 

- (void)addSomeObserver { 
    [self addObserver:self forKeyPath:@"frame" options:0 context:&myContextPointer]; 
} 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 
    if (context != &myContextPointer) { 
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 
    } 
    else { 
     // This message is for me, do whatever I want with it. 
    } 
}         
+0

Sería mejor hacer algo como 'static int myContext;' y luego '[self addObserver: self forKeyPath: @" frame "options: 0 context: & myContext];'. De esta manera, se garantiza que la dirección apuntada es única. –

+0

Oh, tienes razón, debe ser absolutamente '& myContextPointer', no solo' myContextPointer'. Pero no veo por qué un 'int' sería mejor que un' void * '. –

+0

Nada mejor que usar un int, no creo. Es un poco más corto para escribir 'static int myContext;' que para escribir 'static void * myContext;'. (-: Supongo que también podría decir que el compilador le recuerda que debe usar '& myContext' en lugar de simplemente' myContext' - si el tipo 'myContext' es' int', el compilador advertirá que estás haciendo un entero incompatible con la conversión del puntero. –