32

I am relatively new to KVO, so there is a good chance that I am violating some fundamental rule. I am using Core Data.

My app crashes with the following message: And what I can't understand is why a CGImage is getting involved in observing a value that is set on a MeasurementPointer object.

        *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<CGImage 0x276fc0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: measurementDescriptor
Observed object: <MeasurementPointer: 0x8201640> (entity: MeasurementPointer; id: 0x8200410 <x-coredata://EBEE0687-D67D-4B03-8C95-F4C60CFDC20F/MeasurementPointer/p75> ; data: {
    measurementDescriptor = "0x260fd0 <x-coredata://EBEE0687-D67D-4B03-8C95-F4C60CFDC20F/MeasurementDescriptor/p22>";
})
Change: {
    kind = 1;
    new = "<MeasurementDescriptor: 0x262530> (entity: MeasurementDescriptor; id: 0x260fd0 <x-coredata://EBEE0687-D67D-4B03-8C95-F4C60CFDC20F/MeasurementDescriptor/p22> ; data: {\n    measurementName = Temperature;\n    measurementUnits = \"\\U00b0C\";\n    sortString = nil;\n})";
}
Context: 0x0'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x30897ed3 __exceptionPreprocess + 114
    1   libobjc.A.dylib                     0x3002f811 objc_exception_throw + 24
    2   CoreFoundation                      0x30897d15 +[NSException raise:format:arguments:] + 68
    3   CoreFoundation                      0x30897d4f +[NSException raise:format:] + 34
    4   Foundation                          0x34a13779 -[NSObject(NSKeyValueObserving) observeValueForKeyPath:ofObject:change:context:] + 60
    5   Foundation                          0x349b6acd NSKeyValueNotifyObserver + 216
    6   Foundation                          0x349b6775 NSKeyValueDidChange + 236
    7   Foundation                          0x349ae489 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 76
    8   CoreData                            0x3165b577 _PF_ManagedObject_DidChangeValueForKeyIndex + 102
    9   CoreData                            0x3165ac51 _sharedIMPL_setvfk_core + 184
    10  CoreData                            0x3165dc83 _svfk_0 + 10
    11  SPARKvue                            0x000479f1 -[MeasurementViewController doneAction:] + 152
    12  CoreFoundation                      0x3083f719 -[NSObject(NSObject) performSelector:withObject:withObject:] + 24
    13  UIKit                               0x31eb1141 -[UIApplication sendAction:to:from:forEvent:] + 84
    14  UIKit                               0x31f08315 -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 92
    15  CoreFoundation                      0x3083f719 -[NSObject(NSObject) performSelector:withObject:withObject:] + 24
    16  UIKit                               0x31eb1141 -[UIApplication sendAction:to:from:forEvent:] + 84
    17  UIKit                               0x31eb10e1 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 32
    18  UIKit                               0x31eb10b3 -[UIControl sendAction:to:forEvent:] + 38
    19  UIKit                               0x31eb0e05 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 356
    20  UIKit                               0x31eb1453 -[UIControl touchesEnded:withEvent:] + 342
    21  UIKit                               0x31eafddd -[UIWindow _sendTouchesForEvent:] + 368
    22  UIKit                               0x31eaf757 -[UIWindow sendEvent:] + 262
    23  UIKit                               0x31eaa9ff -[UIApplication sendEvent:] + 298
    24  UIKit                               0x31eaa337 _UIApplicationHandleEvent + 5110
    25  GraphicsServices                    0x31e4504b PurpleEventCallback + 666
    26  CoreFoundation                      0x3082cce3 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 26
    27  CoreFoundation                      0x3082cca7 __CFRunLoopDoSource1 + 166
    28  CoreFoundation                      0x3081f56d __CFRunLoopRun + 520
    29  CoreFoundation                      0x3081f277 CFRunLoopRunSpecific + 230
    30  CoreFoundation                      0x3081f17f CFRunLoopRunInMode + 58
    31  GraphicsServices                    0x31e445f3 GSEventRunModal + 114
    32  GraphicsServices                    0x31e4469f GSEventRun + 62
    33  UIKit                               0x31e51123 -[UIApplication _run] + 402
    34  UIKit                               0x31e4f12f UIApplicationMain + 670
    35  SPARKvue                            0x000031ff main + 70
    36  SPARKvue                            0x000031b4 start + 40
)
terminate called after throwing an instance of 'NSException'
Program received signal:  “SIGABRT”.

All that is happening to trigger this is:

[[self measurementPointer] setMeasurementDescriptor:descriptor];

Given this,

[[meterDisplay measurementPointer] addObserver:self 
            forKeyPath:@"measurementDescriptor"
            options:NSKeyValueObservingOptionNew
            context:nil];

Basically, MeasurementPointer objects point to MeasurementDescriptor objects - and both are NSManagedObject subclasses. MeasurementDescriptor objects describe a specific 'measurement' and 'unit' combination (e.g., "Temperature (°C)" or "Wind Speed (mph)"). MeasurementDescriptors are something like singletons to the extent that there is only one for each unique measurement-unit combo.

MeasurementPointers are referenced by other objects - both Model objects and Controller objects. A MeasurementPointer references a MeasurementDescriptor. Many objects are interested in knowing when a MeasurementPointer starts referencing a new/different MeasurementDescriptor. Such a change might cause a graph display's axis to change, for example. Or, in the code above, might cause a meter display to show a different sample (from a selected set of samples).

I think that fundamental problem is that a CGImage is receiving a message that is not intended for it... unfortunately, this is intermittent, so I have not been able to find a pattern that triggers it.

Marcus S. Zarra
  • 46,571
  • 9
  • 101
  • 182
westsider
  • 4,967
  • 5
  • 36
  • 51
  • 1
    This is probably because of an over-release somewhere else. The CGImage was allocated in the spot where some other object should have been. – Firoze Lafeer Nov 08 '10 at 01:03
  • That was what I was afraid of... the strange thing is that it's *always* a CGImage. Or, at least, today it was CGImage 6 or 7 times. I guess I'll turn on NSZombieEnabled and check this out. Thanks. – westsider Nov 08 '10 at 01:13

5 Answers5

42

You have an object that got dealloc'ed and did not stop observing another object. Walk through all of your -addObserver... calls and make sure they are matched with -removeObserver... calls at least in the -dealloc and possibly in the -viewDidUnload depending on your application structure.

Felixyz
  • 19,053
  • 14
  • 65
  • 60
Marcus S. Zarra
  • 46,571
  • 9
  • 101
  • 182
  • 1
    you are right. It wasn't a Core Data issue. I should have posted sooner - but fixing this problem uncovered another one. With NSZombieEnabled turned on, I was able to get helpful warning about deallocated object being sent messages. – westsider Nov 09 '10 at 17:07
  • I got bit by this by using some demo code that wasn't written to correctly un-observe something when it went away. Google brought me right here. Hail the mighty google. – Warren P Feb 22 '13 at 21:44
  • My app uses ARC where to remove the observer? – Pawan Sharma May 27 '13 at 05:58
  • 5
    in the `-dealloc`, just don't call `[super dealloc]` at the end. – Marcus S. Zarra May 27 '13 at 18:04
  • Update for apps targeting iOS >= 9. From `addObserver` docs: If your app targets iOS 9.0 and later or macOS 10.11 and later, you do not need to unregister an observer that you created with this function. If you forget or are unable to remove an observer, the system cleans up the next time it would have posted to it. – RawKnee Feb 01 '23 at 19:25
11

I saw this error when I sent the observeValueForKeyPath method to super, which I had not registered as an observer for the change. Apple docs say "Be sure to call the superclass's implementation [of observeValueForKeyPath] if it implements it."

My fix was to change:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
  if ([keyPath isEqualToString:kPropertyThatChanges]) {
    ...
  }
  [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}

to:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
  if ([keyPath isEqualToString:kPropertyThatChanges]) {
    ...
  }
}

In swift:

func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (keyPath == kPropertyThatChanges) {
    }
    super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
To:
func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (keyPath == kPropertyThatChanges) {
    }
}
Sunil Sharma
  • 2,653
  • 1
  • 25
  • 36
Rose Perrone
  • 61,572
  • 58
  • 208
  • 243
  • I think this answer is as valid as the accepted answer. – danh Nov 08 '15 at 01:55
  • 2
    You shouldn't remove the call to `super`, risking KVO from other keyPath to not work if `super` is also observing. What you should do is to put call to `super` in side `else` ``` if ([keyPath isEqualToString:kPropertyThatChanges]) { ... } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } – Luong Huy Duc Nov 10 '16 at 10:10
3

I ran into this problem by accidentally passing the target in as the observer (instead of self) like so:

[self.someView addObserver:self.someView forKeyPath:@"key" options:0 context:nil];

The error message was not at all helpful in identifying this so just thought I'd post in case anyone else does the same thing.

devios1
  • 36,899
  • 45
  • 162
  • 260
1

I had the same problem, but in my case I was observing a different context. Then I put all in the same context and the crash was gone. I hope this helps someone.

neowinston
  • 7,584
  • 10
  • 52
  • 83
1

For me I forgot to add in override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) to actually do something with the keyPath getting observed.

Here's an example of the Swift version. Add the observers in viewDidLoad and remove them in deinit:

lazy var firstNameTextField: UITextField = {
    let textField = UITextField()
    // configure your textField
    return textField
}()

lazy var lastNameTextField: UITextField = {
    let textField = UITextField()
    // configure your textField
    return textField
}()

override func viewDidLoad() {
    super.viewDidLoad()

    // 1. add your observers here
    firstNameTextField.addObserver(self, forKeyPath: "text", options: [.old, .new], context: nil)
    lastNameTextField.addObserver(self, forKeyPath: "text", options: [.old, .new], context: nil)
}

// 2. ***IMPORTANT you must add this function or it will crash***
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

    if keyPath == "text" {
        print("do something when the textField's text your observing changes")
    }
}

 // 3. remove them in deinit
deinit {
    firstNameTextField.removeObserver(self, forKeyPath: "text", context: nil)
    lastNameTextField.removeObserver(self, forKeyPath: "text", context: nil)
    print("DEINIT")
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256