NSOperationQueue时iOS中常用的任务调度机制。在创建一个复杂任务的时候,我们通常都需要编写NSOperation的子类。在大部分情况下,重写main方法就可以满足要求。main方法执行完毕后,系统就会认为这个operation完成了。
有时候情况并没有这么简单。我们需要在operation中调用异步的API,这个API会通过一个block或者代理通知我们结果。这时只靠覆盖main方法就显得力不从心了。因为异步API尚未执行完毕,main方法并不会等待任务执行完毕,而是立即返回,系统就认为operation已经完成了。
怎么解决这个问题呢?我想到AFNetworking中有同样的案例,于是参考了其中的实现,设计了一个基于异步任务的operation。
我们需要覆盖start方法。这个方法的作用有点类似于main方法,在这里完成具体的任务。那么系统怎么知道我们的任务开始执行,或者完成了呢?系统会通过KVO的形式,监听operation的一些属性。我们可以重新实现这些属性,这样系统就可以监听operation执行的状态。
我们需要重新实现这些属性:
@property (readonly, getter=isReady) BOOL ready; @property (readonly, getter=isExecuting) BOOL executing; @property (readonly, getter=isFinished) BOOL finished;
它们都是只读属性。我们可以简单的重写它们,返回我们想要的值。但是,如何通知KVO系统它们的值发生了变化呢?
NSObject的这一对方法能够帮助我们,可以利用它们手动通知系统某个属性发生了变化。
- (void)willChangeValueForKey:(NSString *)key; - (void)didChangeValueForKey:(NSString *)key;
下面就是完整的代码。这里只用了一个NSTimer模拟一个异步的任务。在state变化时,我们需要通知KVO系统operation的状态发生了变化。这一步很重要,我刚开始忽略了手动通知KVO,导致任务永远无法完成(即使start中的任务全部执行完毕)。
typedef NS_ENUM(NSInteger, MyOperationState) { MyOperationStateReady, MyOperationStateExecuting, MyOperationStateFinished }; @interface MyOperation : NSOperation @property (nonatomic, strong) NSTimer *exeTimer; @property (nonatomic, assign) MyOperationState state; // 用来记录operation的状态 @property (nonatomic, strong) NSLock *lock; // 加锁保证线程安全 @end @implementation MyOperation - (instancetype)init { self = [super init]; if (self) { self.lock = [NSLock new]; [self willChangeValueForKey:@"isReady"]; self.state = MyOperationStateReady; [self willChangeValueForKey:@"isReady"]; } return self; } - (void)start { [self.lock lock]; if (!self.finished && self.state == MyOperationStateReady) {
// 触发isExecuting属性的KVO观察者,这样系统就知道这个operation已经开始执行了
[self willChangeValueForKey:@"isExecuting"]; self.state = MyOperationStateExecuting; [self didChangeValueForKey:@"isExecuting"];
// 这里用一个timer模拟一个耗时的任务 self.exeTimer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(finish) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.exeTimer forMode:NSRunLoopCommonModes]; } [self.lock unlock]; } - (void)cancel { [self.lock lock]; if (!self.isFinished && !self.cancelled) { [super cancel]; [self.exeTimer invalidate]; } [self.lock unlock]; } - (BOOL)isReady { return self.state == MyOperationStateReady; } - (BOOL)isExecuting { return self.state == MyOperationStateExecuting; } - (BOOL)isFinished { return self.state == MyOperationStateFinished; } - (BOOL)isAsynchronous { return YES; } - (BOOL)isConcurrent { return YES; } - (void)finish { [self.lock lock];
// 触发isFinished属性的KVO观察者,这样系统就知道这个operation已经执行完毕
[self willChangeValueForKey:@"isFinished"];
self.state = MyOperationStateFinished;
[self didChangeValueForKey:@"isFinished"];
[self.lock unlock];
}
@end