一、什么是NSOperation?

NSOperation是苹果提供的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更加的面向对象、代码可读性更高、可控性更强,很屌的是加入了操作依赖。

默认情况下,NSOperation单独使用时只能同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步执行。讲到这里,我们不难发现GCD和NSOperation实现的方式很像,其实这更像是废话,NSOperation本身就是基于GCD的封装,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列,前面《iOS多线程开发之GCD(上篇)》中已经阐述过GCD的实质:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。这样我们也可说NSOperation的本质就是:定义想执行的任务(NSOperation)并追加到适当的NSOperationQueue中。

      使用 NSOperation、NSOperationQueue能带来的好处:

1.可添加完成的代码块,在操作完成后执行。

2.添加操作之间的依赖关系,方便的控制执行顺序。

3.设定操作执行的优先级(并不能改变依赖关系,只是修改已经确定的operation执行顺序,依赖关系优先)。

4.可以很方便的取消一个操作的执行。

5.使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

二、NSOperation使用

1、创建任务

NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。但它不能直接用来封装任务,只能通过它的子类来封装,一般的我们可以使用:NSBlockOperation、NSInvocationOperation或者定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。

(1)NSInvocationOperation

- (void)invocationOperation{

    NSLog(@"start - %@",[NSThread currentThread]);

    // 创建NSInvocationOperation对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil]; // 调用start方法开始执行操作
[op start]; NSLog(@"end - %@",[NSThread currentThread]);
} - (void)testRun{
NSLog(@"invocationOperation -- %@", [NSThread currentThread]);
}

执行结果:

-- ::59.327 beck.wang[:] start - <NSThread: 0x6100000614c0>{number = , name = main}
-- ::59.328 beck.wang[:] invocationOperation -- <NSThread: 0x6100000614c0>{number = , name = main}
-- ::59.328 beck.wang[:] end - <NSThread: 0x6100000614c0>{number = , name = main}

分析:单独使用NSInvocationOperation的情况下,NSInvocationOperation在主线程同步执行操作,并没有开启新线程。

(2)NSBlockOperation

- (void)blockOperation{

    NSLog(@"start - %@",[NSThread currentThread]);

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation--%@", [NSThread currentThread]);
}]; NSLog(@"end - %@",[NSThread currentThread]); [op start];
}

打印结果:

-- ::25.436 beck.wang[:] start - <NSThread: 0x6100000653c0>{number = , name = main}
-- ::25.436 beck.wang[:] end - <NSThread: 0x6100000653c0>{number = , name = main}
-- ::25.436 beck.wang[:] blockOperation--<NSThread: 0x6100000653c0>{number = , name = main}

分析:单独使用NSBlockOperation的情况下,NSBlockOperation也是在主线程执行操作,没有开启新线程。

值得注意的是:NSBlockOperation还提供了一个方法addExecutionBlock:,通过addExecutionBlock:就可以为NSBlockOperation添加额外的操作,这些额外的操作就会在其他线程并发执行。

- (void)blockOperation{

    NSLog(@"start - %@",[NSThread currentThread]);

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation--%@", [NSThread currentThread]);
}]; // 添加额外任务(在子线程执行)
[op addExecutionBlock:^{
NSLog(@"addTask1---%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"addTask2---%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"addTask3---%@", [NSThread currentThread]);
}]; NSLog(@"end - %@",[NSThread currentThread]); [op start];
}

打印结果:

-- ::02.009 beck.wang[:] start - <NSThread: 0x60000007cdc0>{number = , name = main}
-- ::02.009 beck.wang[:] end - <NSThread: 0x60000007cdc0>{number = , name = main}
-- ::02.010 beck.wang[:] blockOperation--<NSThread: 0x60000007cdc0>{number = , name = main}
-- ::02.010 beck.wang[:] addTask1---<NSThread: 0x618000260e00>{number = , name = (null)}
-- ::02.010 beck.wang[:] addTask3---<NSThread: 0x600000263200>{number = , name = (null)}
-- ::02.010 beck.wang[:] addTask2---<NSThread: 0x610000264600>{number = , name = (null)}

分析:blockOperationWithBlock任务在主线程中执行,addExecutionBlock的任务在新开线程中执行。

(3)自定义NSOperation子类--重写main方法即可

.h

@interface ZTOperation : NSOperation

@end

.m

@implementation ZTOperation

- (void)main{

    // 在这里可以自定义任务
NSLog(@"ZTOperation--%@",[NSThread currentThread]);
}
@end

ViewController

ZTOperation *zt = [[ZTOperation alloc] init];
[zt start];

打印结果:

-- ::58.824 beck.wang[:] ZTOperation--<NSThread: 0x60000007a940>{number = , name = main}

分析:任务在主线程中执行,不开启新线程。

2、创建队列

NSOperationQueue一共有两种队列:主队列、其他队列。其中其他队列同时包含了串行、并发功能,通过设置最大并发数maxConcurrentOperationCount来实现串行、并发!

(1)主队列  -- 任务在主线程中执行

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

(2)其他队列 -- 任务在子线程中执行

NSOperationQueue *elseQueue = [[NSOperationQueue alloc] init];

3、NSOperation  +  NSOperationQueue (任务追加到队列)

// 添加单个操作:
- (void)addOperation:(NSOperation *)op; // 添加多个操作:
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0); // 添加block操作:
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

代码示例:

- (void)addOperationToQueue
{ NSLog(@"start - %@",[NSThread currentThread]); // 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 创建NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil]; // 创建NSBlockOperation
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task002 -- %@", [NSThread currentThread]);
}]; // 添加操作到队列中: addOperation:
[queue addOperation:op1];
[queue addOperation:op2]; // 添加操作到队列中:addOperationWithBlock:
[queue addOperationWithBlock:^{
NSLog(@"task003-----%@", [NSThread currentThread]);
}]; NSLog(@"end - %@",[NSThread currentThread]);
} - (void)testRun{
NSLog(@"task001 -- %@", [NSThread currentThread]);
}

打印结果:

-- ::51.669 beck.wang[:] start - <NSThread: 0x610000077640>{number = , name = main}
-- ::51.670 beck.wang[:] end - <NSThread: 0x610000077640>{number = , name = main}
-- ::51.670 beck.wang[:] task003-----<NSThread: 0x600000077200>{number = , name = (null)}
-- ::51.670 beck.wang[:] task002 -- <NSThread: 0x61800007e080>{number = , name = (null)}
-- ::51.670 beck.wang[:] task001 -- <NSThread: 0x61000007e1c0>{number = , name = (null)}

分析:开启新线程,并发执行。

三、NSOperationQueue管理

1、队列的取消、暂停、恢复

- (void)cancel;                           NSOperation提供的方法,可取消单个操作

- (void)cancelAllOperations;         NSOperationQueue提供的方法,可以取消队列的所有操作

- (void)setSuspended:(BOOL)b;    可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列

- (BOOL)isSuspended;                判断暂停状态

暂停或取消并不能使正在执行的操作立即暂停或取消,而是当前操作执行完后不再执行新的操作。两者的区别在于暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

 取消操作知识延伸:

对于一个自定义NSOperation来说,想要取消操作,除了需要向其发送cancel消息之外,还必须手动实现某些方法。

         start和main方法中,都必须周期性的对cancelled属性进行判断,如果YES,则立即退出当前,终止操作。 其中,start方法中的判断使得尚未开始的操作及时退出; main方法中的判断使得正在执行当中的操作退出。
         start--检查是否取消-->main--检查是否取消

  • 取消单一操作:cancel / NSOperation

  • 取消队列中的操作:cancelAllOperations / NSOperationQueue

  • 取消正在执行的操作:需要在main方法中周期性的检查cancelled状态
/**
在合适的时机检查cancelled状态,退出当前任务.
三个切入点:
1. 开始执行任务之前
2. 每个for循环中
3. 阶段性的任务之间
*/
- (void)main
{
// 正式开始执行任务之前检查
if (self.isCancelled) {
return;
} for (int i = ; i < ; i++) {
// 每个循环开始之前检查
if (self.isCancelled) {
NSLog(@"退出当前任务");
return;
} // 一个漫长的任务
} // 阶段性任务之间检查
[self processLongTask];
}
  • 取消位于队列中等待执行的操作:start方法的默认实现会检查cancelled状态,如果为YES,则立即退出,不会调用main方法。

2、最大并发数 maxConcurrentOperationCount

maxConcurrentOperationCount = - 1  表示不限制,默认并发执行;

maxConcurrentOperationCount = 1 表示最大并发数为1,串行执行;

maxConcurrentOperationCount > ([count] > =1)  表示并发执行,min[count,系统限制]。

代码示例:

- (void)operationQueue
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 设置最大并发操作数
// queue.maxConcurrentOperationCount = - 1; // 并发执行
// queue.maxConcurrentOperationCount = 1; // 同步执行
queue.maxConcurrentOperationCount = ; // 并发执行 [queue addOperationWithBlock:^{
NSLog(@"task1-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"task2-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"task3-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"task4-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"task5-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"task6-----%@", [NSThread currentThread]);
}];
}

打印结果:

// queue.maxConcurrentOperationCount = - 1

-- ::39.554 beck.wang[:] task2-----<NSThread: 0x61800006d340>{number = , name = (null)}
-- ::39.554 beck.wang[:] task3-----<NSThread: 0x6080000751c0>{number = , name = (null)}
-- ::39.554 beck.wang[:] task4-----<NSThread: 0x610000071c00>{number = , name = (null)}
-- ::39.554 beck.wang[:] task5-----<NSThread: 0x60000006ea40>{number = , name = (null)}
-- ::39.554 beck.wang[:] task1-----<NSThread: 0x608000073500>{number = , name = (null)}
-- ::39.554 beck.wang[:] task6-----<NSThread: 0x610000071c80>{number = , name = (null)} // 分析:线程数为6,并发执行 -----------------------------------分割线---------------------------------------------- // queue.maxConcurrentOperationCount = 1 -- ::04.365 beck.wang[:] task1-----<NSThread: 0x60800007c880>{number = , name = (null)}
-- ::04.365 beck.wang[:] task2-----<NSThread: 0x60800007c880>{number = , name = (null)}
-- ::04.365 beck.wang[:] task3-----<NSThread: 0x60800007c880>{number = , name = (null)}
-- ::04.365 beck.wang[:] task4-----<NSThread: 0x60800007c880>{number = , name = (null)}
-- ::04.366 beck.wang[:] task5-----<NSThread: 0x60800007c880>{number = , name = (null)}
-- ::04.366 beck.wang[:] task6-----<NSThread: 0x60800007c880>{number = , name = (null)} // 分析:线程个数为1,同步执行 -----------------------------------分割线---------------------------------------------- // queue.maxConcurrentOperationCount = 2 -- ::26.162 beck.wang[:] task2-----<NSThread: 0x608000079740>{number = , name = (null)}
-- ::26.162 beck.wang[:] task1-----<NSThread: 0x6100000770c0>{number = , name = (null)}
-- ::26.162 beck.wang[:] task4-----<NSThread: 0x608000079740>{number = , name = (null)}
-- ::26.162 beck.wang[:] task3-----<NSThread: 0x6100000770c0>{number = , name = (null)}
-- ::26.162 beck.wang[:] task5-----<NSThread: 0x608000079740>{number = , name = (null)}
-- ::26.163 beck.wang[:] task6-----<NSThread: 0x6100000770c0>{number = , name = (null)} // 分析:线程个数为2,并发执行

很明显,通过设置maxConcurrentOperationCount就能实现并发、串行功能是不是比GCD轻松多了!

3、操作依赖

NSOperation中我们可以为操作分解为若干个小的任务,通过添加他们之间的依赖关系进行操作,这个经常用到!这也是NSOperation吸引人的地方,不需要像GCD那样使用复杂的代码实现,addDependency就可以搞定!

- (void)addDependency
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
sleep();
NSLog(@"task1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2-----%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task3-----%@", [NSThread currentThread]);
}]; // op2依赖于op1 执行顺序op1->op2 必须放在[添加操作队列]之前
[op2 addDependency:op1]; // 忌循环依赖 op2已经依赖于op1,切不可再让op1依赖于op2,形成循环依赖
//[op1 addDependency:op2]; // 添加操作队列
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
     打印结果:

-- ::02.011 beck.wang[:] task3-----<NSThread: 0x61800006d740>{number = , name = (null)}
-- ::04.085 beck.wang[:] task1-----<NSThread: 0x60000006f040>{number = , name = (null)}
-- ::04.085 beck.wang[:] task2-----<NSThread: 0x61800006d740>{number = , name = (null)}

分析:task2一定在task1后面执行,因为执行task1前设置了线程等待2s,所有task3最早执行。

4、操作优先级

NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = ,
NSOperationQueuePriorityHigh = ,
NSOperationQueuePriorityVeryHigh =

5、操作的监听

可以监听一个操作是否执行完毕,如下载图片,需要在下载第一张图片后才能下载第二张图片,这里就可以设置监听。

- (void)addListing{

    NSOperationQueue *queue=[[NSOperationQueue alloc]init];

    NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
for (int i=; i<; i++) {
NSLog(@"下载图片1-%@",[NSThread currentThread]);
}
}]; // 监听操作的执行完毕
operation.completionBlock=^{
// 继续进行下载图片操作
NSLog(@"--下载图片2--");
}; [queue addOperation:operation];
}

执行结果:

-- ::43.833 beck.wang[:] 下载图片1-<NSThread: 0x61800007a340>{number = , name = (null)}
-- ::43.834 beck.wang[:] 下载图片1-<NSThread: 0x61800007a340>{number = , name = (null)}
-- ::43.834 beck.wang[:] 下载图片1-<NSThread: 0x61800007a340>{number = , name = (null)}
-- ::43.834 beck.wang[:] --下载图片2--

分析:下载图片1完成后才会执行下载图片2,这里类似知识点3中的添加依赖。

最新文章

  1. iOS第三方库管理工具
  2. IOS开发之--NSPredicate
  3. [设计模式] javascript 之 模板方法模式
  4. Java入门到精通——基础篇之面向对象
  5. KMP_Best Reward
  6. (转)印度建全球最大生物识别数据库,MongoDB安全受质疑
  7. 「操作系统」:Linker Use static Libraries
  8. WM_PARENTNOTIFY的作用(不完全)
  9. TWaver 2D+GIS+3D的试用和在线Demo
  10. Objective-C Runtime 文档翻译
  11. Shell 脚本中调用另一个 Shell 脚本的三种方式
  12. eclipse常用工具
  13. bower学习总结
  14. 在maven 2工程中加入iTextAsian支持(maven添加自定义jar包到本地仓库)
  15. idea13注册机
  16. 【转】Apache服务器的下载与安装
  17. uva 1513(线段树)
  18. python日常碎碎念
  19. Hibernate(三)结构-配置文件-实体映射及配置文件
  20. sublime text3及插件安装过程

热门文章

  1. Travelling Businessmen Problem
  2. zabbix自定义添加主机
  3. 关于RL78 系列的bootloader
  4. Python——Pandas 时间序列数据处理
  5. You are attempting to install the android sdk inside your android studio installation
  6. Django2.0——django-filter: TypeError at *** __init__() got an unexpected keyword argument &#39;name&#39;
  7. [转]Apache漏洞利用与安全加固实例分析
  8. BinarySearchTree(二叉搜索树)原理及C++代码实现
  9. [USACO09DEC]牛收费路径Cow Toll Paths(floyd、加路径上最大点权值的最短路径)
  10. 4)模板输出方法和 fetch总结