如果没有别的,请理解这一点:NSOperation 的行为没有什么神奇之处。 NSOperationQueue 只是使用 Key Value Observation 来监控操作。这并不容易的唯一原因是使用的键与 Objective-C 2.0 约定所说的不一样,因此标准的合成设置器不起作用。
结果是当你定义你的NSOperation子类时,你需要提供asynchronous、executing和finished。最后两个需要您的帮助才能正常工作。
听起来很复杂?不是,只是详细信息。一路上的每一步都很简单且有意义,但在你把它们都做对之前它实际上是行不通的。
首先是表头:
//
// MyOperation.h
#import <Foundation/Foundation.h>
@interface MyOperation : NSOperation
@property(readonly, getter=isAsynchronous) BOOL asynchronous;
@property(readonly, getter=isExecuting) BOOL executing;
@property(readonly, getter=isFinished) BOOL finished;
@end
当然,您可以在此处将executing 和finished 定义为readwrite,因此您无需在实现中将它们重新定义为readwrite。但我想知道只有我的操作才能改变它们的状态。
现在实施。这里有几个步骤:
- 将
finished 和executing 属性重新定义为读/写。
- 完全提供
executing 和finished 的实现,手动提供正确的KVO 消息传递(所以isExecuting、setExecuting:、isFinished 和setFinished:)。
- 使用
@synthesize 为executing 和finished ivars 提供存储空间。
- 提供
asynchronous的实现
(请注意,此代码可能会滚动一点。)
//
// MyOperation.m
#import "MyOperation.h"
@interface MyOperation()
@property(readwrite) BOOL executing;
@property(readwrite) BOOL finished;
@end
@implementation MyOperation
// Provide your own start.
- (void)start {
if (self.cancelled) {
self.finished = YES;
return;
}
NSLog(@"Starting %@", self);
self.executing = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(@"Finished %@", self);
self.executing = NO;
self.finished = YES;
});
}
// The rest of this is boilerplate.
- (BOOL)isAsynchronous {
return YES;
}
@synthesize executing = _executing;
- (BOOL)isExecuting {
@synchronized(self) {
return _executing;
}
}
- (void)setExecuting:(BOOL)executing {
@synchronized(self) {
if (executing != _executing) {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
}
}
@synthesize finished = _finished;
- (BOOL)isFinished {
@synchronized(self) {
return _finished;
}
}
- (void)setFinished:(BOOL)finished {
@synchronized(self) {
if (finished != _finished) {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
}
}
@end
实际上没有必要在 setter 中检查(例如)executing != _executing。通过调用willChangeValueForKey 自动提供正确的行为,盲目地更改值,然后调用didChangeValueForKey。但是条件意味着您可以在赋值上设置断点,并且仅在值更改时停止,而且我发现这对于在实践中调试我的操作非常有用。
我还看到通过在executing 和finished 属性之上提供自定义状态来实现这一点。当然,这非常好,并且在某些方面更好……但它也需要比这个示例更多的 KVO 知识,这已经足够了。
最后,请注意,一旦操作开始,我没有添加对取消的支持。为此,您必须覆盖cancel(或者更准确地说,观察isCancelled 的值)并处理它。这会使我的简单 start 示例复杂很多。
我在命令行控制台应用程序中运行此代码,方法是将 15 个操作添加到 maxConcurrentOperationCount 为 5 的队列中,然后使用 waitUntilAllOperationsAreFinished 在队列上等待完成(这就是我为 @987654355 使用后台队列的原因@ 在我的start)。这是输出:
2019-01-22 13:29:32.897893-0800 test[86762:4812871] Starting <MyOperation: 0x10058d2d0>
2019-01-22 13:29:32.897893-0800 test[86762:4812872] Starting <MyOperation: 0x10058d710>
2019-01-22 13:29:32.897903-0800 test[86762:4812873] Starting <MyOperation: 0x100589930>
2019-01-22 13:29:32.898161-0800 test[86762:4812871] Starting <MyOperation: 0x10058edc0>
2019-01-22 13:29:32.898166-0800 test[86762:4812873] Starting <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898487-0800 test[86762:4812872] Finished <MyOperation: 0x100589930>
2019-01-22 13:29:37.898489-0800 test[86762:4812870] Finished <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898548-0800 test[86762:4812874] Finished <MyOperation: 0x10058edc0>
2019-01-22 13:29:37.898797-0800 test[86762:4812870] Starting <MyOperation: 0x100590000>
2019-01-22 13:29:37.899160-0800 test[86762:4812870] Finished <MyOperation: 0x10058d710>
2019-01-22 13:29:37.899651-0800 test[86762:4812870] Starting <MyOperation: 0x1005901a0>
2019-01-22 13:29:37.899933-0800 test[86762:4812874] Starting <MyOperation: 0x100590340>
2019-01-22 13:29:37.900133-0800 test[86762:4812871] Finished <MyOperation: 0x10058d2d0>
2019-01-22 13:29:37.900504-0800 test[86762:4812871] Starting <MyOperation: 0x100590680>
2019-01-22 13:29:37.900583-0800 test[86762:4812874] Starting <MyOperation: 0x1005904e0>
2019-01-22 13:29:42.899325-0800 test[86762:4812871] Finished <MyOperation: 0x100590000>
2019-01-22 13:29:42.899541-0800 test[86762:4812874] Starting <MyOperation: 0x100590820>
2019-01-22 13:29:43.393291-0800 test[86762:4812871] Finished <MyOperation: 0x1005901a0>
2019-01-22 13:29:43.393298-0800 test[86762:4812874] Finished <MyOperation: 0x100590340>
2019-01-22 13:29:43.394531-0800 test[86762:4812874] Finished <MyOperation: 0x1005904e0>
2019-01-22 13:29:43.395380-0800 test[86762:4812874] Finished <MyOperation: 0x100590680>
2019-01-22 13:29:43.396359-0800 test[86762:4812874] Starting <MyOperation: 0x1005909c0>
2019-01-22 13:29:43.397440-0800 test[86762:4812872] Starting <MyOperation: 0x100590b60>
2019-01-22 13:29:43.397891-0800 test[86762:4812874] Starting <MyOperation: 0x100590d00>
2019-01-22 13:29:43.399711-0800 test[86762:4812872] Starting <MyOperation: 0x100590ea0>
2019-01-22 13:29:47.900058-0800 test[86762:4812984] Finished <MyOperation: 0x100590820>
2019-01-22 13:29:48.892953-0800 test[86762:4812872] Finished <MyOperation: 0x100590d00>
2019-01-22 13:29:48.892970-0800 test[86762:4812871] Finished <MyOperation: 0x100590b60>
2019-01-22 13:29:48.893019-0800 test[86762:4813163] Finished <MyOperation: 0x100590ea0>
2019-01-22 13:29:48.893562-0800 test[86762:4812984] Finished <MyOperation: 0x1005909c0>
Program ended with exit code: 0