- NSOperation 简介
- NSOperation 和 GCD 比较
- NSInvocationOperation使用
- NSBlockOperation使用
- NSOperationQueue使用
- 其他方法
操作和操作队列、使用步骤和基本使用方法、控制串行/并发执行、
NSOperation 操作依赖和优先级、线程间的通信、线程同步和线程安全,以及 NSOperation、NSOperationQueue 常用属性和方法归纳。
NSOperation 简介:
NSOperation 是苹果公司对 GCD 的封装,简单易用,完全面向对象,
NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。
优点:
- 可添加完成的代码块,在操作完成后执行。
- 添加操作之间的依赖关系,方便控制执行顺序。
- 设定操作执行的优先级。
- 可以很方便的取消一个操作的执行。
- 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
NSOperation 和 GCD 比较:
在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念。
操作(Operation):
执行操作的意思,换句话说就是你在线程中执行的那段代码。
在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
操作队列(Operation Queues):
这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。
操作步骤:
NSOperation 单独使用:
NSOperation只是一个抽象类,不能封装任务,他的两个子类可以封装任务:NSInvocationOperation和NSBlockOperation。或者使用自定义继承自 NSOperation 的子类
创建一个NSOperation后,start启动,cancel取消。
把NSOperation添加到NSOperationQueue中自动调用start()方法
NSBlockOperation通过addExecutionBlock可以给Operation添加多个执行Block。
单独使用 NSOperation 的情况下系统同步执行操作NSOperation 和 NSOperationQueue
将要执行的任务封装到一个 NSOperation 对象中。
将NSOperation添加到一个 NSOperationQueue 对象中,系统会⾃动将NSOperationQueue中的NSOperation取出来,将取出的NSOperation封装的操作放到⼀条新线程中执⾏.
首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
NSInvocationOperation使用:
/**
* 使用子类 NSInvocationOperation
*/
- (void)useInvocationOperation {
// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.调用 start 方法开始执行操作
[op start];
}
/**
* 任务1
*/
- (void)task1 {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}
操作对象默认在主线程中执行(操作是在当前线程执行的,如果在其他线程中执行操作,则打印结果为其他线程。),只有将NSOperation添加到NSOperationQueue队列中才会开启新的线程,才会异步执行操作。如果操作没有放到队列queue中,都是同步执行。(所以上面在主线程中执行)
(比如两个循环AB,A执行完再执行B,不会交叉执行)
// 在其他线程使用子类 NSInvocationOperation
[NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];
可以看到:在其他线程中单独使用子类 NSInvocationOperation,操作是在当前调用的其他线程执行的,并没有开启新线程。
NSBlockOperation使用:
/**
* 使用子类 NSBlockOperation
*/
- (void)useBlockOperation {
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 2.调用 start 方法开始执行操作
[op start];
}
(在没有使用 NSOperationQueue、在主线程中单独使用 NSBlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。)
只要NSBlockOperation封装的操作数 > 1,(就会异步执行操作,会开启新线程。(可以控制串行还是并发)
注意:
NSBlockOperation和NSInvocationOperation 。因为代码是在主线程中调用的,所以打印结果为主线程。如果在其他线程中执行操作,则打印结果为其他线程。
NSBlockOperation 还提供了一个方法 addExecutionBlock:,通过 addExecutionBlock: 就可以为 NSBlockOperation 添加额外的操作。这些操作(包括 blockOperationWithBlock 中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成。
/**
* 使用子类 NSBlockOperation
* 调用方法 AddExecutionBlock:
*/
- (void)useBlockOperationAddExecutionBlock {
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 2.添加额外的操作
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"6---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"7---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"8---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 3.调用 start 方法开始执行操作
[op start];
}
可以看出:使用子类 NSBlockOperation,并调用方法 AddExecutionBlock: 的情况下,blockOperationWithBlock:方法中的操作 和 addExecutionBlock: 中的操作是在不同的线程中异步执行的。而且,这次执行结果中 blockOperationWithBlock:方法中的操作也不是在当前线程(主线程)中执行的。从而印证了blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行。
如果添加的操作多的话,blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行。这是由系统决定的,并不是说添加到 blockOperationWithBlock: 中的操作一定会在当前线程中执行。
一般情况下,如果一个 NSBlockOperation 对象封装了多个操作。NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。
NSOperationQueue使用:
NSOperationQueue 一共有两种队列:主队列、自定义队列。
凡是添加到主队列中的操作,都会放到主线程中执行。NSOperationQueue *queue = [NSOperationQueue mainQueue];
自定义队列同时包含了串行、并发功能。添加到自定义队列中的操作,就会自动放到子线程中执行。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperationQueue的作⽤:NSOperation可以调⽤start⽅法来执⾏任务,但(分别创建多个)默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中,自动执行操作,自动开启线程
- 两种添加方法:
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
/**
* 使用 addOperation: 将操作加入到操作队列中
*/
- (void)addOperationToQueue {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
// 使用 NSInvocationOperation 创建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 使用 NSInvocationOperation 创建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
// 使用 NSBlockOperation 创建操作3
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op3 addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 3.使用 addOperation: 添加所有操作到队列中
[queue addOperation:op1]; // [op1 start]
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}
系统自动将NSOperationqueue中的NSOperation对象取出,将其封装的操作放到一条新的线程中执行。上面的代码示例中,一共有四个任务,operation1和operation2分别有一个任务,operation3有两个任务。一共四个任务,开启了四条线程。通过任务执行的时间全部都是273可以看出,这些任务是并行执行的。
使用 NSOperation 子类创建操作,并使用 addOperation: 将操作加入到操作队列后能够开启新线程,进行并发执行。
提示:队列的取出是有顺序的,与打印结果并不矛盾。这就好比,选手A,BC虽然起跑的顺序是先A,后B,然后C,但是到达终点的顺序却不一定是A,B在前,C在后。
- (void)addOperationWithBlock:(void (^)(void))block;
无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。
/**
* 使用 addOperationWithBlock: 将操作加入到操作队列中
*/
- (void)addOperationWithBlockToQueue {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.使用 addOperationWithBlock: 添加操作到队列中
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}
可以看出:使用 addOperationWithBlock: 将操作加入到操作队列后能够开启新线程,进行并发执行。
NSOperationQueue封装了线程的管理 可以管理队列中任务的并发数量,执行顺序,队列的暂停开启等。使用 addOperationWithBlock: 将操作加入到操作队列后能够开启新线程,进行并发执行。
最大并发数,取消、暂停、恢复,操作优先级,添加任务、添加依赖,添加监听
一、并发数 NSOperationQueue
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
//最大并发操作数 用来控制一个特定队列中可以有多少个操作同时参与并发执行。
如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的
最大并发数不要乱写(5以内),不要开太多,否则可能会影响UI,让UI变卡。
最大并发数, 不能设置为0, 否则任务不会被执行
maxConcurrentOperationCount 控制串行 并发
maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。= 1 并不是只开一条子线程(通常会开两条子线程,循环回收复用)
二、队列的取消,暂停和恢复 NSOperationQueue
- (void)cancelAllOperations;
- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- (BOOL)isSuspended; //当前状态
任务只要被取消, 就不会再恢复了
取消任务和暂停任务一样, 不会取消当前正在执行的任务, 只能取消还未执行的任务。暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。
1.如果在任务执行的过程中暂停队列中的任务, 那么当前正在执行的任务并不会被暂停, 而是会暂停队列中的下一个任务
2.恢复任务, 是从队列第一个没有被执行过的任务开始恢复
也可以调用NSOperation的- (void)cancel⽅法取消单个操作
比如:在tableview界面,在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。
//NSOperation
- (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
- (BOOL)isFinished; 判断操作是否已经结束。
- (BOOL)isCancelled; 判断操作是否已经标记为取消。
- (BOOL)isExecuting; 判断操作是否正在在运行。
三、操作优先级 NSOperation
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
优先级高的任务,调用的几率会更大。
queuePriority 属性决定了进入准备就绪状态下的操作之间的开始执行顺序。并且,优先级不能取代依赖关系。
四、操作依赖 NSOperation
[operationB addDependency:operationA]; // 操作B后执行
注意:不能循环依赖(不能A依赖于B,B又依赖于A)。
注意:一定要在添加到NSOperationQueue之前,进行设置。可添加多个依赖
除了同一quere操作间建立依赖关系,当然也可以在不同queue的NSOperation之间创建依赖关系
通过removeDependency方法来删除依赖对象
五、操作的监听 NSOperation
(void (^)(void))completionBlock;
-
(void)setCompletionBlock:(void (^)(void))block;
在上一个任务执行完后,会执行operation.completionBlock=^{}代码段,且是在当前线程执行。//操作同步
op1.completionBlock = ^(){
NSLog(@"任务1执行完毕");};
参考:
https://www.jianshu.com/p/4b1d77054b35
https://blog.csdn.net/ssirreplaceable/article/details/53357334
http://www.cocoachina.com/game/20151201/14517.html
http://www.cnblogs.com/wendingding/p/3809042.html
http://www.cnblogs.com/wendingding/p/3809150.html