使用 KVO 时出现 NSInternalInconsistencyException

Posted

技术标签:

【中文标题】使用 KVO 时出现 NSInternalInconsistencyException【英文标题】:NSInternalInconsistencyException when using KVO 【发布时间】:2011-09-29 15:49:56 【问题描述】:

我正在尝试使用从 iPhone 教程书中获得的 KVO 示例,但出现此异常

   Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<UINavigationController: 0x139630>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: dataUpdated
Observed object: <FilterDetailsController: 0x1b9930>
Change: 
    kind = 1;

    Context: 0x0'
     Call stack at first throw:
    (
        0   CoreFoundation                      0x320d3987 __exceptionPreprocess + 114
        1   libobjc.A.dylib                     0x3271849d objc_exception_throw + 24
        2   CoreFoundation                      0x320d37c9 +[NSException raise:format:arguments:] + 68
        3   CoreFoundation                      0x320d3803 +[NSException raise:format:] + 34
        4   Foundation                          0x35c316e9 -[NSObject(NSKeyValueObserving) observeValueForKeyPath:ofObject:change:context:] + 60
        5   Foundation                          0x35bd4a3d NSKeyValueNotifyObserver + 216
        6   Foundation                          0x35bd46e5 NSKeyValueDidChange + 236
        7   Foundation                          0x35bcc3f5 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 76
        8   Foundation                          0x35c30d87 _NSSetObjectValueAndNotify + 98
        9   Lucid Dreaming App                  0x000108e3 -[FilterDetailsController adjustFilterDetails:] + 138
        10  CoreFoundation                      0x3207afed -[NSObject(NSObject) performSelector:withObject:withObject:] + 24
        11  UIKit                               0x323b3ea5 -[UIApplication sendAction:to:from:forEvent:] + 84
        12  UIKit                               0x323b3e45 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 32
        13  UIKit                               0x323b3e17 -[UIControl sendAction:to:forEvent:] + 38
        14  UIKit                               0x323b3b69 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 356
        15  UIKit                               0x323b43c7 -[UIControl touchesEnded:withEvent:] + 342
        16  UIKit                               0x323a9d4d -[UIWindow _sendTouchesForEvent:] + 368
        17  UIKit                               0x323a96c7 -[UIWindow sendEvent:] + 262
        18  UIKit                               0x3239499f -[UIApplication sendEvent:] + 298
        19  UIKit                               0x323942df _UIApplicationHandleEvent + 5090
        20  GraphicsServices                    0x35472f03 PurpleEventCallback + 666
        21  CoreFoundation                      0x320686ff __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 26
        22  CoreFoundation                      0x320686c3 __CFRunLoopDoSource1 + 166
        23  CoreFoundation                      0x3205af7d __CFRunLoopRun + 520
        24  CoreFoundation                      0x3205ac87 CFRunLoopRunSpecific + 230
        25  CoreFoundation                      0x3205ab8f CFRunLoopRunInMode + 58
        26  GraphicsServices                    0x354724ab GSEventRunModal + 114
        27  GraphicsServices                    0x35472557 GSEventRun + 62
        28  UIKit                               0x323c7d21 -[UIApplication _run] + 412
        29  UIKit  ...

我从

开始
[advancedController addObserver:self.navigationController forKeyPath:@"dataUpdated" options:0 context:nil];

在 self.navigation 控制器中,我定义了观察方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 


    NSLog(@"2 Observed change for: %@",keyPath);
//    if (context == <#context#>) 
//        <#code to be executed upon observing keypath#>
//     else 
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
//    

在 advancedController 我有一个 NSNumber 属性

 @property(nonatomic,retain)NSNumber* dataUpdated;

当我点击一个按钮时,我希望观察者会触发。

  - (IBAction)adjustFilterDetails:(id)sender 
        self.dataUpdated = [NSNumber numberWithInt:[self.dataUpdated intValue]+1];
    

我是否需要实现一些协议或明确声明我将更新该值?我读到 NSKeyValueObserving 是一个“非正式”协议。感谢您的帮助!

我已经找到了自己问题的答案。我在 ViewController 中定义了 KVO 协议方法,但是,我不小心为 View 控制器注册了 Navigation 控制器来观察值。通过像这样获取视图控制器来找到正确的对象:

 [advancedController addObserver:(RootViewController*)self.navigationController. topViewController forKeyPath:@"dataUpdated" options:0 context:nil];

【问题讨论】:

【参考方案1】:

您的问题是对super 的调用。对于您自己观察到的属性,您不应将此方法传递给super。您注释掉了可以帮助您解决此问题的代码。

可能的正确观察是(注意上下文):

[advancedController addObserver:self.navigationController forKeyPath:@"dataUpdated" options:0 context:self];

那么正确的观察者应该是:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 

    if (context == self) 
        NSLog(@"2 Observed change for: %@",keyPath);
     else 
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    

Dave Dribin 在Proper Key-Value Observer Usage 中提供了另一种正确使用context 的方法,以及为什么需要它的背景故事。

【讨论】:

感谢您的修复。这适用于其他类(例如我的自定义数据收集类),但是当我尝试让一个 UIViewController 观察另一个 UIViewController 中对变量所做的更改时,甚至在调用该方法之前就会引发异常。所以原始示例中的 NSLog 语句永远不会被执行,对 super 的调用也不会被执行。这是 UIViewController 的某种问题吗? 此异常表明您正在访问NSObject 中的observeValueForKeyPath:...。这要么意味着你让错误的类成为观察者,要么你在自己观察到的事情上调用super observeValueForKeyPath:...

以上是关于使用 KVO 时出现 NSInternalInconsistencyException的主要内容,如果未能解决你的问题,请参考以下文章

操作观察期间的 Swift 4 KVO 致命错误

OC中的KVO

实体计数上的神奇记录 KVO

在没有调用 viewDidLoad 的情况下调用了 Dealloc(删除 KVO 观察者时崩溃)

iOS KVO监听数组元素的变化

运行 adb 命令时出现问题 / 使用平板设备进行开发时出现问题