使用自定义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
,事情似乎正常。
答案 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
似乎有效,但我个人完全放弃isExecuting
和isFinished
属性,而是定义executing
和finished
属性正如凯文所说,这与现代惯例更为一致:
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
然后我为这两个属性编写自定义setter,它们执行必要的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;
答案 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
未观察到isFinished
或isExecuting
,它正在观察finished
和executing
。
isFinished
只是属性finished
的合成get访问器。将为此属性发送自动键值观察通知,除非您的子类通过实施+automaticallyNotifiesObserversForKey
或+automaticallyNotifiesObserversOf<Key>
专门选择退出自动KVO通知以返回NO。如果您尚未选择停用自动KVO通知,则无需使用will/DidChangeValueForKey:
执行手动通知。在您的情况下,您发送了isFinished
和isExecuting
的手动通知,这些通知不是NSOperationQueue
观察到的关键路径。
executing
和finished
是正确的密钥路径,他们应该发送自动KVO通知。
如果您对KVO 真正偏执并希望发送get访问者密钥路径(例如isFinished
)的通知,请将您的属性注册为关键路径的依赖项:
+ (NSSet *) keyPathsForValuesAffectingIsFinished {
NSSet *result = [NSSet setWithObject:@"finished"];
return result;
}