Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

In my UIScrollView subclass, I'm observing frame changes:

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

My observeValueForKeyPath:ofObject:change:context: implementation is as follows:

- (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

But I get exception with this code:

*** 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'

Does it mean UIScrollView implements observeValueForKeyPath:ofObject:change:context: but throws the above exception?

If so, how can I properly implement observeValueForKeyPath:ofObject:change:context: so that I can both handle my interested changes and give superclass a chance to handle its interested changes?

You should add a context value when you add the observer. In your -observeValueForKeyPath method, check the context parameter. If it is not the context you passed when you added the observer, then you know this message is not intended for your subclass, and you can safely pass it on to the superclass. If it is the same value, then you know it's intended for you, and you should not pass it on to super.

Like this:

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.
                It would be better to do something like static int myContext; and then [self addObserver:self forKeyPath:@"frame" options:0 context:&myContext];.  This way, the address pointed to is guaranteed to be unique.
– Nate Chandler
                Sep 18, 2012 at 18:19
                Oh, you're right, it should absolutely be &myContextPointer, not just myContextPointer. But I don't see why an int would be any better than a void *.
– BJ Homer
                Sep 18, 2012 at 19:05
                Nothing better about using an int, I don't think.  It's just slightly shorter to type static int myContext; than to type static void *myContext;.  (-:  I guess you could say, also, that it let's the compiler remind you that you should be using &myContext rather than just myContext--if myContext's type is int the compiler will warn that you're doing an incompatible integer to pointer conversion.
– Nate Chandler
                Sep 19, 2012 at 0:50
                Honestly, I usually do static void *myContext = &myContext;, which is nice because then myContext == &myContext, and you can use either one. But I didn't feel like trying to explain that little mess in an answer mostly unrelated to the topic.
– BJ Homer
                Sep 19, 2012 at 4:12
                @BJHomer nice, I've never come across this static void *myContext = &myContext; thing but now it seems so obvious. Thanks!
– Mark
                Mar 26, 2020 at 3:16

Edit: BJ Homer's answer is probably a better approach to take here; I forgot all about the context parameter!

Even though calling the super implementation is by-the-book, it seems like calling observeValueForKeyPath:ofObject:change:context: on UIKit classes that don't actually observe the fields in question throws an NSInternalInconsistency exception (not the NSInvalidArgumentException you would get with an unrecognized selector). The key string in the exception that suggests this to me is "received but not handled".

As far as I know, there's no well-documented way to find out if an object observes another object on a given key path. There may be partially-documented ways such as the -observationInfo property which is said to carry information on the observers of an object, but you're on your own there—it's a void *.

So as I see it, you've got two options: either don't call the super implementation or use an @try/@catch/@finally block to ignore that specific type of NSInternalInconsistencyException. The second option is probably more future-proof, but I have a hunch that some detective work could get you more satisfying results via the first option.

The article you linked to is talking about another thing: the superclass does NOT implement observeValueForKeyPath:ofObject:change:context:. But here as you can see from the code, UIScrollView does implement it, otherwise super method will not be called. – an0 Jul 4, 2011 at 21:27 Even if it does implement the method, it might have code that detects unobserved keys and throws exceptions for them—thus the NSInternalInconsistency exception "received but not handled", which is different from the NSInvalidArgumentException "unrecognized selector sent to instance". – Joe Osborn Jul 5, 2011 at 14:12 That's what I'm asking. So the default implementation of observeValueForKeyPath:ofObject:change:context: in NSObject throws exception? If so, there is nothing we can do to detect whether superclass wants to be notified of key value changes, isn't there? – an0 Jul 5, 2011 at 17:21 The implementation in some UIKit classes, at least, certainly seems to. My guess, then, is "no, there's no (safe) way to detect it". The safest way is probably to look for that exception in an @try. Off-the-record, I would say that you might try looking at the -observationInfo property of the object you're interested in (self) on the off chance that it might help you find out what keys the parent class is observing. – Joe Osborn Jul 5, 2011 at 18:37 It's actually not hard to know if the message is meant for you... just look use the 'context' parameter. – BJ Homer Aug 23, 2012 at 18:55

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.