NSOperation是基于GCD的面向对象封装,在各大开源库里面我们常常看到它的身影。它的使用很简单易懂,基本上你点进Api就会用了,本文就最常用的功能进行讲解,更多的多线程原理还是看本系列文章的GCD部分。
一、NSBlockOperation和NSInvocationOperation
这两个类都是NSOperation
的子类,它们的区别可能就是一个是用block
回调,一个是用NSInvocation
回调,我们通常直接使用的也是这两个类,下面代码示意如何创建任务:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{}];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(respondsToOperation:) object:nil];
都不用多说了,这就是两个任务,非常简单,当然,它还有一系列的方法表示状态:executing
、finished
、cancelled
,以及开启和取消:start
、cancel
,和NSThread
一样有main
方法可重写。
二、NSOperationQueue 队列
和GCD一样,NSOperation保留了队列的概念,但是没有具体的并行和串行的概念了,但是我们可以实现它。
先来看这样一段代码:
NSLog(@"主线程开始");
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务0 %@", [NSThread currentThread]);
}];
for (int i = 1; i < 5; i++) {
[blockOperation addExecutionBlock:^{
NSLog(@"任务%d: %@", i, [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}];
}
[blockOperation start];
NSLog(@"主线程结束");
主线程开始
任务3: {number = 5, name = (null)}
任务2: {number = 4, name = (null)}
任务1: {number = 3, name = (null)}
任务0 {number = 1, name = main}
任务4: {number = 1, name = main}
主线程结束
上机打印出来的效果来看,这些任务是并行执行的,但是会阻塞主线程直到任务全部都执行完成。显然这不能满足我们的全部需求,所以我们把任务加入队列试试(加入队列任务自动执行):
NSLog(@"主线程开始");
NSOperationQueue *queue = [NSOperationQueue new];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务0 %@", [NSThread currentThread]);
}];
for (int i = 1; i < 5; i ++) {
[blockOperation addExecutionBlock:^{
NSLog(@"任务%d %@", i, [NSThread currentThread]);
}];
}
[queue addOperation:blockOperation];
//[blockOperation waitUntilFinished];
NSLog(@"主线程结束");
主线程开始
主线程结束
任务0 {number = 3, name = (null)}
任务1 {number = 5, name = (null)}
任务4 {number = 4, name = (null)}
任务3 {number = 3, name = (null)}
任务2 {number = 5, name = (null)}
从打印结果来看,这是并行队列+异步任务,这基本满足我们后台执行耗时任务的需求了。
从打印结果来看,这是并行队列+异步任务,这基本满足我们后台执行耗时任务的需求了。
你们应该也注意到了我注释了一句代码[blockOperation waitUntilFinished]
;,现在将它打开,运行得到的结果和方法的名字一样,它会阻塞当前线程直到任务全部结束,当然NSOperationQueue
也有这么一个方法:waitUntilAllOperationsAreFinished
。
maxConcurrentOperationCount
最大并发数,通过设置这个我们可以有效的控制并发任务的数量,从而对性能进行有效的控制。
三、addDependency 添加任务依赖
我们给NSOperation添加依赖的目的,主要是实现串行队列的效果。
NSLog(@"主线程开始");
NSOperationQueue *queue = [NSOperationQueue new];
NSBlockOperation *operation0 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1: %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2: %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务3: %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务4: %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[operation1 addDependency:operation0];
[operation2 addDependency:operation1];
[operation3 addDependency:operation2];
//不要添加相互依赖。理论上不管任务在任何队列都可以添加依赖,不过不建议这么做。
[queue addOperations:@[operation0, operation1, operation2, operation3] waitUntilFinished:YES];
NSLog(@"主线程结束");
主线程开始
任务1: {number = 3, name = (null)}
任务2: {number = 4, name = (null)}
任务3: {number = 3, name = (null)}
任务4: {number = 4, name = (null)}
主线程结束
实验证明,达到了我们预期的效果,任务1到任务4都是串行执行的,并且addOperations: waitUntilFinished:方法若第二个参数为YES,会阻塞主线程。
写在后面
NSOperation的用法不见得有GCD简洁,但它符合面向对象的编程思想,而且便于在我们的业务代码中穿梭,所以它的使用场景还是比较多的,没什么难点可言,本文列举的用法已经可言解决大多数的场景了,如果想多了解,直接看一遍苹果官网的API就OK了?。