为什么NSOperation会禁用自动键值观察?

时间:2010-08-26 08:15:31

标签: key-value-observing nsoperation

使用自定义NSOperation子类时,我注意到[NSOperation automaticallyNotifiesObserversForKey]类方法(至少对某些键路径返回NO)禁用了自动键值观察。因此,NSOperation子类中的代码被手动调用willChangeValueForKey:didChange…所占据,这在网络上的许多代码示例中都可见。

为什么NSOperation会这样做?通过自动KVO支持,人们可以简单地声明操作生命周期标志(isExecuting等)的属性,并通过访问器触发KVO事件,即。以下代码:

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

......可以替换为:

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

某处有捕获吗?我只是覆盖automaticallyNotifiesObserversForKey以返回YES,事情似乎正常。

4 个答案:

答案 0 :(得分:17)

最可能的解释是kvo键与标准约定不匹配。通常情况下,有-isExecuting-setExecuting:等方法,其中关键路径为@"executing"。在NSOperation的情况下,关键路径是@"isExecuting"

另一种可能性是,大多数NSOperations实际上没有名为-setIsExecuting:的方法来更改该值。相反,它们将执行/完成标志基于其他内部状态。在这种情况下,绝对需要使用显式的willChange / didChange通知。例如,如果我有一个包装NSURLConnection的NSOperation,我可能有2个ivars,一个名为data,用于保存下载的数据,另一个名为connection,用于保存NSURLConnection,我可以实现像这样的吸气剂:

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

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

现在我的-start方法可以使用

[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"];

开始执行,

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

完成。

答案 1 :(得分:6)

虽然我同意覆盖automaticallyNotifiesObserversForKey似乎有效,但我个人完全放弃isExecutingisFinished属性,而是定义executingfinished属性正如凯文所说,这与现代惯例更为一致:

@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, readwrite, getter = isFinished)  BOOL finished;

然后我为这两个属性编写自定义setter,它们执行必要的isExecutingisFinished通知:

- (void)setExecuting:(BOOL)executing
{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)setFinished:(BOOL)finished
{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

这会产生:

  • 更习惯的BOOL财产声明;
  • 自定义设置器满足NSOperation要求的奇怪通知;和
  • 我现在可以在整个操作实施过程中使用executingfinished设置器,而不会通过通知乱丢我的代码。

我必须承认我喜欢压倒一切automaticallyNotifiesObserversForKey的优雅,但我只是担心会产生意想不到的后果。


注意,如果在iOS 8或Yosemite中执行此操作,您还必须在@implementation中明确合成这些属性:

@synthesize finished  = _finished;
@synthesize executing = _executing;

答案 2 :(得分:0)

我不知道你为什么谈论NSOperation不能使用自动KVO。但我只是想验证一下,因此它可以使用KVO。

[self addObserver:self
       forKeyPath:@"isReady"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

[self addObserver:self
       forKeyPath:@"isExecuting"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

[self addObserver:self
       forKeyPath:@"isFinished"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

[self addObserver:self
       forKeyPath:@"isCancelled"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

...

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == &ctxKVO_CSDownloadOperation) {
        NSLog(@"KVO: %@", keyPath);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

结果:

2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isExecuting : 0
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] KVO: isExecuting
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isFinished : 0
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isFinished
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] isCancelled : 0
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isCancelled
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isExecuting : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isExecuting
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isFinished : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isFinished
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isCancelled : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isCancelled
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] isExecuting : 0

所以我对这个问题和答案感到很困惑......

答案 3 :(得分:-3)

NSOperationQueue未观察到isFinishedisExecuting,它正在观察finishedexecuting

isFinished只是属性finished的合成get访问器。将为此属性发送自动键值观察通知,除非您的子类通过实施+automaticallyNotifiesObserversForKey+automaticallyNotifiesObserversOf<Key>专门选择退出自动KVO通知以返回NO。如果您尚未选择停用自动KVO通知,则无需使用will/DidChangeValueForKey:执行手动通知。在您的情况下,您发送了isFinishedisExecuting的手动通知,这些通知不是NSOperationQueue观察到的关键路径。

TL; DR:这些不是NSOperationQueue寻找的关键路径。

These are not the key paths you are looking for

executingfinished是正确的密钥路径,他们应该发送自动KVO通知。

如果您对KVO 真正偏执并希望发送get访问者密钥路径(例如isFinished)的通知,请将您的属性注册为关键路径的依赖项:

+ (NSSet *) keyPathsForValuesAffectingIsFinished {
    NSSet   *result = [NSSet setWithObject:@"finished"];
    return result;
}