4 回答
最可能的解释是 kvo 键不符合标准约定。通常有一个方法,比如-isExecuting
和-setExecuting:
,关键路径在哪里@"executing"
。在 NSOperation 的情况下,键路径是@"isExecuting"
。
另一种可能性是大多数 NSOperations 实际上并没有命名方法-setIsExecuting:
来更改该值。相反,它们将执行/完成标志基于其他内部状态。在这种情况下,绝对需要使用显式 willChange/didChange 通知。例如,如果我有一个包装 NSURLConnection 的 NSOperation,我可能有 2 个 ivars,一个命名data
保存下载的数据,一个命名connection
保存 NSURLConnection,我可以像这样实现 getter:
- (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"];
完成。
虽然我同意覆盖automaticallyNotifiesObserversForKey
似乎有效,但我个人完全放弃了isExecuting
andisFinished
属性,而是定义了executing
andfinished
属性,正如 Kevin 所建议的,它更符合现代惯例:
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
然后我为这两个属性编写自定义设置器,它们执行必要的isExecuting
通知isFinished
:
- (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
需要的奇怪通知;和 - 我现在可以在整个操作实现中使用
executing
和finished
设置器,而不会在我的代码中乱扔通知。
我必须承认我喜欢压倒一切的优雅automaticallyNotifiesObserversForKey
,但我只是担心意想不到的后果。
请注意,如果在 iOS 8 或 Yosemite 中执行此操作,您还必须在您的@implementation
:
@synthesize finished = _finished;
@synthesize executing = _executing;
我不知道你为什么说 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
所以我真的很困惑这个问题和答案......
NSOperationQueue
不是观察或isFinished
,isExecuting
它是观察finished
和executing
。
isFinished
只是属性的综合 get 访问器finished
。+automaticallyNotifiesObserversForKey
除非您的子类通过实现或+automaticallyNotifiesObserversOf<Key>
返回 NO明确选择退出自动 KVO 通知,否则将为该属性发送自动键值观察通知。如果您没有选择退出自动 KVO 通知,则无需使用will/DidChangeValueForKey:
. isFinished
在您的情况下,您正在为和发送手动通知isExecuting
,这不是NSOperationQueue
观察到的关键路径。
TL;DR:这些不是 NSOperationQueue 正在寻找的关键路径。
executing
并且finished
是正确的关键路径,它们应该发送自动 KVO 通知。
如果你真的对 KVO 有疑虑,并且想要为 get 访问器键路径发送通知,例如isFinished
,将你的属性注册为键路径的依赖项:
+ (NSSet *) keyPathsForValuesAffectingIsFinished {
NSSet *result = [NSSet setWithObject:@"finished"];
return result;
}