Что не так с этим наблюдениемValueForKeyPath: fromObject: change: context: implementation?

В моем подклассе UIScrollView я наблюдаю изменения кадров:

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

Моя реализация observeValueForKeyPath:ofObject:change:context: выглядит следующим образом:

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

Но я получаю исключение с этим кодом:

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

Означает ли это, что UIScrollView реализует observeValueForKeyPath:ofObject:change:context:, но выдает исключение выше?

Если да, то как я могу правильно реализовать observeValueForKeyPath:ofObject:change:context:, чтобы я мог обрабатывать мои заинтересованные изменения и дать суперклассу возможность обработать его интересующие изменения?

Ответ 1

Изменить: ответ BJ Гомера, вероятно, лучший подход к этому; Я забыл все о параметре контекста!

Несмотря на то, что вызов супер-реализации - это книга, она кажется, вызывающей observeValueForKeyPath:ofObject:change:context: на UIKit классах, Фактически наблюдаемые поля задают исключение NSInternalInconsistency (а не NSInvalidArgumentException, которое вы получите с непризнанным селектором). Строка ключа в исключении, которая предлагает это мне, "получена, но не обрабатывается".

Насколько я знаю, нет хорошо документированного способа узнать, наблюдает ли объект другой объект по заданному пути ключа. Могут быть частично документированные способы, такие как свойство -observationInfo, которое, как говорят, несет информацию о наблюдателях объекта, но вы сами по себе - это a void *.

Итак, как я вижу, у вас есть два варианта: либо не вызывать реализацию super, либо использовать блок @try/@catch/@finally, чтобы игнорировать этот тип NSInternalInconsistencyException, Второй вариант, вероятно, более перспективен, но я подозреваю, что некоторые детективные работы могут получить более удовлетворительные результаты с помощью первого варианта.

Ответ 2

При добавлении наблюдателя вы должны добавить значение context. В вашем методе -observeValueForKeyPath проверьте параметр контекста. Если это не тот контекст, который вы передали при добавлении наблюдателя, то вы знаете, что это сообщение не предназначено для вашего подкласса, и вы можете безопасно передать его суперклассу. Если это одно и то же значение, то вы знаете, что оно предназначено для вас, и вы не должны передавать его супер.

Вот так:

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.
    }
}