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.
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.
While I agree that overriding automaticallyNotifiesObserversForKey
appears to work, but I personally forgo the isExecuting
and isFinished
properties altogether and instead define executing
and finished
properties, which, as Kevin suggests, is more consistent with modern conventions:
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
I then write custom setters for these two properties, which do the necessary isExecuting
and isFinished
notifications:
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
This yields:
BOOL
property declaration;NSOperation
requires; andexecuting
and finished
setters throughout my operation implementation, without littering my code with notifications.I must confess that I like the elegance of overriding automaticallyNotifiesObserversForKey
, but I just worry about unintended consequences.
Note, if doing this in iOS 8 or Yosemite, you will also have to explicitly synthesize these properties in your @implementation
:
@synthesize finished = _finished;
@synthesize executing = _executing;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With