Search code examples
iphoneobjective-ccocoakey-value-observing

Cocoa KVO:observeValueForKeyPath:ofObject:change:context: is called twice for one value change


I have a NSOperation subclass, which implements setFinished: to generate KVO notification:

-(void)setFinished:(BOOL)isFinished
{
    LogError(@"%@ will will changing isFinished to %d",self,isFinished);
    [self willChangeValueForKey:@"isFinished"];
    LogError(@"%@ did will changing isFinished to %d",self,isFinished);
    _isFinished = isFinished;
    LogError(@"%@ will did changing isFinished to %d",self,isFinished);
    [self didChangeValueForKey:@"isFinished"];
    LogError(@"%@ did did changing isFinished to %d",self,isFinished);
}

I am also sure that I have only add the observer to the operation once.

Strange thing comes, sometimes observeValueForKeyPath:ofObject:change:context: for this object's isFinished key path is called twice, referring to the log, I only find setFinished: called once, and addObserver:forKeyPath: for this operation is only called once.

Also, I found that it's sequence is like this:

will will changing isFinished to 1
did will changing isFinished to 1
will did changing isFinished to 1
calling observeValueForKeyPath for object
did did changing isFinished to 1
calling observeValueForKeyPath for object

So anyone has any clue please?


Solution

  • If you want to take responsibility for calling willChangeValueForKey: and didChangeValueForKey:, you have to tell the system. You can do that in two ways:

    • You can override +automaticallyNotifiesObserversForKey: (a class method) on your class to return NO when the key is finished. This is documented under “Manual Change Notification” in the *Key-Value Observing Programming Guide.

    • You can implement +automaticallyNotifiesObserversOfFinished to return NO. This is documented in the NSKeyValueObserving.h header file, in the comment on +automaticallyNotifiesObserversForKey:.

    If you don't implement one of these class methods properly, the system will automatically notify observers when setFinished: returns. It looks like that's what you're seeing in your log.