Multiple KVO Keys: Why call willChangeValueForKey: twice before didChangeValueForKey:?

770 views Asked by At

I've been trying to fix an issue in our NSOperation subclass and I feel it may be related to our manual change notifications for KVO. All the sources I've checked seem to do the following when updating the NSOperation state:

[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isFinished"];
[self didChangeValueForKey:@"isExecuting"];

In contrast, we have been doing it like this:

[self willChangeValueForKey:@"isExecuting"];
_isExecuting = NO;
[self didChangeValueForKey:@"isExecuting"];

[self willChangeValueForKey:@"isFinished"];   
_isFinished = YES;
[self didChangeValueForKey:@"isFinished"];

Can anybody tell me why the former seems to be the recommended way of doing this?

It also seems that Apple's KVO docs recommend the first approach as well. Unfortunately they don't explain why.(https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-SW3)/

2

There are 2 answers

3
Ken Thomases On

The reason is that, conceptually, the operation is changing both states together. You want observers to be notified only after the internal state has been updated for both properties, so that, when handling the notification, the observers see consistent state.

If you do:

[self willChangeValueForKey:@"isExecuting"];
_isExecuting = NO;
[self didChangeValueForKey:@"isExecuting"];

then observers will get the change notification for the isExecuting property during that didChange... call. If they check the operation properties in their handler, they could see that the operation is not executing (isExecuting returns NO) but also not finished (isFinished still returns NO). That doesn't make sense. The observers could do something odd as a result.

0
user3754239 On

I've implemented NSOperation without following the pattern

[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isFinished"];
[self didChangeValueForKey:@"isExecuting"];

instead by simply doing

self.executing = NO;
self.finished = YES;

.. without problems (never ending operations or the like) when using NSOperationQueues. It seems NSOperationQueue only listens for 'IsFinished' to determine if an NSOperation is truly finished. This other answer explains better.

NSOperation KVO isFinished