KVO for one-to-many but NSNull object passed into

2019-05-10 23:15发布

问题:

I have one managed object with a one-to-many relationship to member class. When I add the observers for members, it worked. When one new member is added to the relationship, the observeValueForKeyPath will be invoked with the new object and change dictionary contains the new member object. However, observeValueForKeyPath will be triggered second time with all values nil and change dictionary new="NULL". What is the second trigger? I set a breakpoint, but not sure who made the trigger.

@interface FooObject : NSManagedObject {}
@property (nonatomic, strong) NSString *fooId;
@property (nonatomic, strong) NSSet* members;
@end

@implementation FooObject
@dynamic fooId;
@dynamic members;

- (NSMutableSet*)membersSet {
    [self willAccessValueForKey:@"members"];

    NSMutableSet *result = (NSMutableSet*)[self mutableSetValueForKey:@"members"];

    [self didAccessValueForKey:@"members"];
    return result;
}

- (void)registerObservers {
    [self addObserver:self
           forKeyPath:@"members"
              options:NSKeyValueObservingOptionNew
              context:nil];
}

- (void)unregisterObservers {
    @try{
        [self removeObserver:self forKeyPath:@"members"];
    }@catch(id anException){
        //do nothing, obviously it wasn't attached because an exception was thrown
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context
    id valueNewSet = [change objectForKey:NSKeyValueChangeNewKey];
    if (![valueNewSet isKindOfClass:[NSSet class]]) {
            // not a NSSet, it contains <null> value
            NSLog(@"%@", change);
            NSLog(@"%@", object);
    }
    if ([[change objectForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeInsertion) {
           // insert change is valid, process the changes
    }

}
@end

Log output:

{
    kind = 1;
    new = "<null>";
}

<FooObject: 0xfa9cc60> (entity: FooObject; id: 0xfa9be00 <x-coredata://39DB31FD-6795-4FDE-B700-819AB22E5170/SHInterest/p6> ; data: {
    fooId = nil;
    members = nil;
})

EDIT 1 I set a breakpoint at NSLog(@"%@", change); This is the stack trace but not really helpful to figure who makes this call.

main -> UIApplicationMain -> NSKeyValueNotifyObserver -> observeValueForKeyPath:ofObject:change:context

EDIT 2 Maybe this is still a bug? http://www.cocoabuilder.com/archive/cocoa/182567-kvo-observevalueforkeypath-not-reflecting-changes.html

回答1:

I am running into the same issue where the "implicit" assignment (and hence the notification of the KVO observer) apparently occurs only as part of deallocating MOs: I save a child MOC, then release it, then iOS releases its MOs. I assume that it then sets one-to-many relationships temporarily to NSNull in the process of deallocating related MOs (where delete rule cascade applies).

So next to change kinds for insertion and deletion my KVO observer now also accepts NSKeyValueChangeSetting and asserts change[NSKeyValueChangeNewKey] == NSNull.null. Call it a pragmatic solution.