Why does NSOperation disable automatic key-value observing?

Viewed 4345

When working with a custom NSOperation subclass I noticed that the automatic key-value observing is disabled by the [NSOperation automaticallyNotifiesObserversForKey] class method (which returns NO at least for some key paths). Because of that the code inside of NSOperation subclasses is littered by manual calls to willChangeValueForKey: and didChange…, as visible in many code samples on the web.

Why does NSOperation do that? With automatic KVO support people could simply declare properties for the operation lifecycle flags (isExecuting etc.) and trigger the KVO events through the accessors, ie. the following code:

[self willChangeValueForKey:@"isExecuting"];
executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];

…could be replaced by this:

[self setIsExecuting:NO];
[self setIsFinished:YES];

Is there a catch somewhere? I just overrode the automaticallyNotifiesObserversForKey to return YES and things seem to work fine.

4 Answers

The most likely explanation is that the kvo keys don't match the standard conventions. Normally one has methods like -isExecuting and -setExecuting:, where the key path is @"executing". In the case of NSOperation, the key path is @"isExecuting" instead.

The other possibility is that most NSOperations don't actually have a method named -setIsExecuting: to change that value. Instead, they base the executing/finished flags on other internal state. In this case, one absolutely needs to use the explicit willChange/didChange notifications. For example, if I have an NSOperation that wraps an NSURLConnection, I may have 2 ivars, one named data that holds the downloaded data, and one named connection which holds the NSURLConnection, and I may implement the getters like so:

- (BOOL)isExecuting {
    return (connection != nil);
}

- (BOOL)isFinished {
    return (data != nil && connection == nil);
}

Now my -start method can use

[self willChangeValueForKey:@"isExecuting"];
data = [[NSMutableData alloc] init]; // doesn't affect executing, but is used later
connection = [[NSURLConnection connectionWithRequest:request delegate:self] retain];
[self didChangeValueForKey:@"isExecuting"];

to start executing, and

[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
[connection cancel];
[connection release];
connection = nil;
[self didChangeValueForKey:@"isFinished"];
[self didChangeValueForKey:@"isExecuting"];

to finish.

Related