iOS多线程-NSOperation
NSOperation是对GCD的高级封装。相对于GCD,使用NSOperation更加符合面对对象的编程习惯,更重要的是,NSOperation提供了更多的特性方便开发者监控和管理要并发执行的任务:
- 可以简便地设置任务之间的依赖关系
- 可以对取消正在执行的任务
- 可以只用KVO的方式来监控任务的当前状态
- 可以针对任务设置优先级
- 可以为任务设置一个完成后执行的competitionBlock
创建NSOperation
NSOperation有两个具体的子类,NSInvocationOperation和NSBlockOperation,创建方法如下(官方Demo):
@implementation MyCustomClass
- (NSOperation*)taskWithData:(id)data {
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(myTaskMethod:) object:data];
return theOp;
}
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
// Perform the task.
}
@end
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Beginning operation.\n");
// Do some work.
}];
NSOperation的同步和异步执行
NSOperation对象调用start方法执行,默认是在调用的线程同步执行的。当一个线程调用start方法时,operation会立即在该线程中执行。当operation结束后线程才会继续执行之后的代码。
对一个异步的operation来说,当调用其start方法时,该operation可能会开启一个新的线程执行,调用一个异步方法或者将block提交到一个dispatch_queue,总之调用的线程会继续执行余下的代码而不会等待operation完成。
NSOperationQueue
大多数情况下并不需要创建一个异步的operation,这需要开发者做很多额外的工作。更普遍的做法是将NSOperation放进NSOperationQueue中去执行。当你将一个operation加进operation queue中,那么无论该operation的asynchronous
属性是什么,调用者都会在一个另外一个的线程中去执行start方法。所以当使用NSOperationQueue来管理执行的话,完全没有必要创建异步的operation。
当operation添加进operation queue后,不需要调用任何方法,operation queue 中的operation就会开始执行。
设置依赖
用法简单,直接上一段代码:
- (void)opDependency{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *calculateOp1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end cal 1, do some update");
}];
[queue addOperation:calculateOp1];
NSOperation *calculateOp2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end cal 2, do some update");
}];
[queue addOperation:calculateOp2];
NSOperation *doAfterPreOpDone = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"do something which depend on op1 and op2");
}];
[doAfterPreOpDone addDependency:calculateOp1];
[doAfterPreOpDone addDependency:calculateOp2];
[queue addOperation:doAfterPreOpDone];
}
运行结果为:
2017-04-20 15:53:06.062 ConcurrentProgram[98523:43310892] start do some calulation 1
2017-04-20 15:53:06.062 ConcurrentProgram[98523:43310890] start do some calulation 2
2017-04-20 15:53:08.134 ConcurrentProgram[98523:43310890] end cal 2, do some update
2017-04-20 15:53:09.137 ConcurrentProgram[98523:43310892] end cal 1, do some update
2017-04-20 15:53:09.137 ConcurrentProgram[98523:43310890] do something which depend on op1 and op2
取消NSOperation
将上面的代码稍微改动一下,测试一下NSOperation的cancel功能。
- (void)opDependency{
__block long i = 0;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *calculateOp1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 1");
while (true) {
i++;
}
}];
[queue addOperation:calculateOp1];
NSOperation *calculateOp2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end cal 2, do some update");
[calculateOp1 cancel];
}];
[queue addOperation:calculateOp2];
NSOperation *doAfterPreOpDone = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"do something which depend on op1 and op2");
NSLog(@"i is %ld",i);
}];
[doAfterPreOpDone addDependency:calculateOp1];
[doAfterPreOpDone addDependency:calculateOp2];
[queue addOperation:doAfterPreOpDone];
}
在calculateOp1中设置了一个无限循环的block,然后我们希望在calculateOp2中去取消掉calculateOp1。预期结果是在calculateOp2中的block执行完以后会立即执行doAfterPreOpDone中的任务。(因为另一个依赖被取消了)
结果如下:
2017-04-20 16:38:01.658 ConcurrentProgram[542:43544549] start do some calulation 1
2017-04-20 16:38:01.658 ConcurrentProgram[542:43544548] start do some calulation 2
2017-04-20 16:38:03.730 ConcurrentProgram[542:43544548] end cal 2, do some update
发现calculateOp1并没有取消而doAfterPreOpDone一直不会执行。
去查阅官方的API文档,发现有这样的一段话:
Canceling an operation does not immediately force it to stop what it is doing. Although respecting the value in the cancelled
property is expected of all operations, your code must explicitly check the value in this property and abort as needed. The default implementation of NSOperation
includes checks for cancellation. For example, if you cancel an operation before its start
method is called, the start
method exits without starting the task.
也就是说调用cancel方法并不会立即停止operation中的动作,只是将cancelled属性设置为YES,真正的取消动作是需要开发者自己实现的。一般来说,要继承NSOperation类,在start方法中不停地检查isCancel属性。在默认的NSOeration的实现中也包括了cancel属性的检查,如果你在operation的start方法之前cancel了,那么该operation就不会执行了。
官方提供了一个示例(将main方法改为start方法效果一样)
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
那么之前的代码相当于:
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
NSLog(@"start do some calulation 1");
while (true) {
i++;
}
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
检查cancel属性的代码在打印之前,在打印之后取消该block就没有效果了。