Search code examples
cocoamacosobserver-patternkey-value-observing

KVO problem "Cannot remove an observer"


I have an NSArrayController linked to a Core Data object, set to Auto Rearrange Content and filtered by a predicate. All is well until I try to nullify a relationship and assign another. At that point, my application crashes and I receive the following error:

Cannot remove an observer for the key path "career.type" from Object, most likely because the value for the key "career" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the Person class.

From snooping around, it appears that having my NSArrayController set to Auto Rearrange Content causes this issue. But I'm trying to resolve the problem without having to resort to manually rearranging the NSArrayController. Here's the pseudo-code that's triggering the error:

object.career = nil;
object.field = (Field *)item;

Here's the predicate my NSArrayController is using:

(career != NIL && career == %@) || (field != NIL && field == %@)

Where %@ for both instances is a CoreData object.

Basically, it looks as though the NSArrayController has an observer set for object.career.type and nullifying the relationship causes an issue when that observer is automatically removed. So I'm wondering if I'm going about this the wrong way? Should I be grabbing a copy of the object, deleting it from the MOC and reinserting it with career set to nil and field set accordingly?

How do I correctly notify the observer that type has been nullified? Note that all attributes and relationships mentioned here use vanilla KVO-compliant getters/setters.


Solution

  • From apples documentation

    Faults and KVO Notifications

    When Core Data turns an object into a fault, key-value observing (KVO) change notifications (see Key-Value Observing Programming Guide) are sent for the object’s properties. If you are observing properties of an object that is turned into a fault and the fault is subsequently realized, you receive change notifications for properties whose values have not in fact changed.

    Although the values are not changing semantically from your perspective, the literal bytes in memory are changing as the object is materialized. The key-value observing mechanism requires Core Data to issue the notification whenever the values change as considered from the perspective of pointer comparison. KVO needs these notifications to track changes across key paths and dependent objects.


    So basically you will get an notification telling you there's a change even if it's not. So you got to check if the object has become an fault. Then remove the old observer and add a new to the same path...

    For me this worked (sample code):

    - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    
        if ([keyPath isEqualToString:@"pageIndex"]) {
    
            // basically remove the observer from the fault object and assign the new
            if([object isFault]) {
                [object removeObserver:self forKeyPath:@"pageIndex"];
                [the_current_instance_returned_by_core_data addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionOld context:NULL];
            } 
    
            // do whatever you want to do on change...
    
        }
    }