苹果在并发编程方面,除了提供有GCD外,还有NSOperation【操作】
和NSOperationQueue【操作队列】
组合。GCD是基于C的底层API,而NSOperation则是封装GCD实现Objective-C API。NSOperationQueue系统给出了许多api,能够很方便的实现GCD需要大量代码的事情【比如:操作队列可以取消在任务处理队列中的任务】。因此,NSOperationQueue被推荐使用。虽然被推荐使用,但个人认为,还是根据自己的编程习惯。如果用着GCD顺手而且也能完全满足自己开发中的需求。就不必非得换用别的。
本文将通过介绍NSOperation和NSOperationQueue的相关用法演示,做以自己日常学习的总结,也希望能够给看到这篇文章的读者们一个入门介绍。
文章内容目录:
- 1,NSOperation和NSOperationQueue的介绍。
- 2,NSInvocationOperation的使用
- 3,NSBlockOperation的使用
- 4,自定义Operation的步骤
- 5,其它
- 6,总结
1、 NSOperation和NSOperationQueue的介绍。
1.1、NSOperation
NSOperation
是系统提供的抽象的基类,我们使用的时候需要使用继承于它的子类。系统为我们提供了两种继承于NSOperation
的子类,分别是NSInvocationOperation
和NSBlockOperation
,大多情况下,我们用这两个系统提供的子类就能满足我们并发编程的任务,但是如果你不想用系统提供的这两个类,那么你可以根据自己的需求来自定义自己的操作类。文章后边会结束自定义操作类的时候的步骤和注意事项。
1.2、NSOperationQueue
-
NSOperationQueue【操作队列】
:用来存放操作的队列。是由GCD提供的一个队列模型的Cocoa抽象。GCD提供了更加底层的控制,而操作队列则在GCD之上实现了一些方便的功能。 - NSOperationQueue操作队列中的任务的执行顺序收到任务的
isReady
【就绪状态】状态和任务的队列优先级影响。这和GCD中的队列FIFO
的执行顺序有很大区别。 - 我们可以通过设置
NSOperationQueue
的最大并发操作数(maxConcurrentOperationCount)来控制任务执行是并发还是串行。 -
NSOperationQueue
有两种不通类型的队列:主队列和自定义队列。主队列在主线程上运行,而自定义队列在后台执行。这两种队列中加入的任务都需要用NSOperation的子类来表示。
注:NSOperation【操作】通俗的讲,就是我们写在程序里的一段代码。
2、 NSInvocationOperation的使用
2.1、手动执行操作。
/**
在主线程中执行
*/
- (void)executeInMainThread
{
NSLog(@"创建操作:%@",[NSThread currentThread]);
//1:创建操作。
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task) object:nil];
//2:开始执行操作。
[op start];
}
/**
在子线程中执行
*/
- (void)executeInNewThread
{
NSLog(@"创建操作:%@",[NSThread currentThread]);
[NSThread detachNewThreadSelector:@selector(executeInMainThread) toTarget:self withObject:nil];
}
- (void)task
{
NSLog(@"执行操作:%@",[NSThread currentThread]);
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"----%@",[NSThread currentThread]);
}
}
由上图运行结果(在主线程和子线程中分别创建操作,并且手动触发)可以看出:手动创建操作并调用start方法触发操作的情况下,操作任务会在创建操作的线程中执行。
2.2、和NSOperationQueue配合使用。
- 2.2.1、将操作添加到主操作队列。
- (void)addMainOperationQueue
{
//获得主操作队列
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
self.mainQueue = mainQueue;
NSLog(@"创建添加任务%@",[NSThread currentThread]);
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
[mainQueue addOperation:op1];
[mainQueue cancelAllOperations];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
[mainQueue addOperation:op2];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
[mainQueue addOperation:op3];
[op3 cancel];//取消某个操作,可以直接调用操作的取消方法cancel。
//取消整个操作队列的所有操作,这个方法好像没有效果???。在主队列中,没有用,如果将操作加入到自定义队列的话,在操作没有开始执行的时候,是能够取消操作的。
// [mainQueue cancelAllOperations];
NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
[mainQueue addOperation:op4];
}
- (void)task:(NSString *)order
{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"任务:%@----%@",order,[NSThread currentThread]);
}
}
由上图运行结果可以看出,当将操作添加到主操作队列时,所有操作会按照添加到队列中的先后顺序串行依次执行。任务在主线程中执行。
- 2.2.2、将操作添加到自定义操作队列。
- (void)addCustomeOperationQueue
{
NSLog(@"创建添加任务%@",[NSThread currentThread]);
NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
customQueue.maxConcurrentOperationCount = 5;//这个属性的设置需在队列中添加任务之前。任务添加到队列后,如果该任务没有依赖关系的话,任务添加到队列后,会直接开始执行。
//加入到自定义队列里的任务,可以通过设置操作队列的 maxConcurrentOperationCount的值来控制操作的串行执行还是并发执行。
//当maxConcurrentOperationCount = 1的时候,是串行执行。 maxConcurrentOperationCount > 1的时候是并发执行,但是这个线程开启的数量最终还是由系统决定的,不是maxConcurrentOperationCount设置为多少,就开多少条线程。默认情况下,自定义队列的maxConcurrentOperationCount值为-1,表示并发执行。
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
[customQueue addOperation:op1];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
//打断点在op1加入队列前后的状态值。
///
///
[customQueue addOperation:op2];
// [customQueue cancelAllOperations];//这个方法只能取消还未开始执行的操作,如果操作已经开始执行,那么该方法依然取消不了。
[customQueue addOperation:op3];
[customQueue addOperation:op4];
}
当将任务添加到自定义队列的时候,会开启子线程,操作会并发执行。
2.3、添加依赖。
- 2.3.1、主队列
//依赖关系的设置需要在任务添加到队列之前。
- (void)addDependenceInMain//这个添加依赖的用途有点类似GCD中的队列组
{
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSLog(@"创建添加任务%@",[NSThread currentThread]);
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
[op1 addDependency:op2];//由此更可以看出,如果添加了依赖关系,在主队列串行执行任务,也不是先进先出的规则。而是按照依赖关系的属性执行。 应该把操作的所有配置都配置好后,再加入队列,因为加入队列后,操作就开始执行了,再进行配置就晚了。
[mainQueue addOperation:op1];
[mainQueue addOperation:op2];
[mainQueue addOperation:op3];
[mainQueue addOperation:op4];
}
由上图的运行结果可以看出,虽然操作op1先于op2加入到队列中,但是给op1添加了依赖关系。必须op2执行完毕后,才会执行op1, 这里的操作队列就和GCD中的队列不一样了,GCD队列是遵守FIFO规则,而这里的队列里的操作则是根据依赖关系等决定。这种依赖关系是单向的,op1依赖于op2,op2的执行与op1没有任何关系。
不能设置双向依赖,如果op1依赖op2,op2又反过来依赖op1,则会出现互相等待的死锁情况。
注意:关于操作队列与操作的相关配置都要在操作加入到队列前配置完全,因为操作加入到队列后,就开始执行操作了。此时,再进行操作或队列的设置很有可能达不到预期的效果。
- 2.3.2、自定义队列
- (void)addDependenceInCustom
{
NSLog(@"创建添加任务%@",[NSThread currentThread]);
NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
customQueue.maxConcurrentOperationCount = 5;
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
//isReady属性如果为YES的时候,该任务处于就绪状态,就等待系统调度执行。如果任务有依赖关系的话,该任务的isReady属性需要在其所属的依赖任务执行完成后,才为YES。
//默认情况下,操作的queuePriority(队列优先级)为0 NSOperationQueuePriorityNormal 正常优先级。
//都处于就绪状态下的操作,才开始按照优先级顺序来执行。 优先级高的是说系统调度的概率会大一些,但是也不能确保完全会按照优先级来,如果要设置操作的执行顺序,最有效也最安全的做法还是设置依赖关系。
[op1 addDependency:op2];
[op2 addDependency:op3];
/*注意:两个任务不能相互依赖,如果相互依赖,则会出现死锁,都执行不了
[op1 addDependency:op2];
[op2 addDependency:op1];
这种相互依赖是错误的。
*/
op1.queuePriority = NSOperationQueuePriorityVeryHigh;
op2.queuePriority = NSOperationQueuePriorityHigh;
op3.queuePriority = NSOperationQueuePriorityLow;
[customQueue addOperation:op1];
[customQueue addOperation:op2];
[customQueue addOperation:op3];
[customQueue addOperation:op4];
}
由上图运行结果可以看出,虽然在自定义队列中操作是并发执行的,但如果添加了依赖关系的话,op1依赖于op2,op1要在op2执行完全后,才会执行。
依赖 VS 队列优先级(queuePriority)
操作有个isReady属性,该属性表示操作时否处于就绪状态,处于就绪状态的操作,只要等待系统调度,就会执行。而操作的就绪状态取决于依赖关系,当op1依赖于op2的时候,如果op2还没执行完,op1的isReady = NO,即op1还处于未就绪状态。同处于就绪状态的操作,此时再比较它们的队列优先级(queuePriority),这样才有意义。
队列中会先执行处于就绪状态的操作,即便处于就绪状态的操作的队列优先级低于未就绪的操作。所以,要控制操作之间的执行顺序,需要使用依赖关系。
3、 NSBlockOperation的使用
3.1、手动执行操作。
- (void)executeInMainThread
{
NSLog(@"创建操作:%@",[NSThread currentThread]);
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[self task];
}];
//NSBlockOperation该操作有个方法能在该操作中持续添加操作任务addExecutionBlock,直到全部的block中的任务都执行完成后,该操作op才算执行完毕。当该操作在addExecutionBlock加入比较多的任务时,该op的block中的(包括blockOperationWithBlock和addExecutionBlock中的操作)会在新开的线程中执行。不一定在创建该op的线程中执行。
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op addExecutionBlock:^{
[self task:@"add"];
}];
[op start];
}
- (void)executeInNewThread
{
[NSThread detachNewThreadSelector:@selector(executeInMainThread) toTarget:self withObject:nil];
}
- (void)task
{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"操作执行:%@",[NSThread currentThread]);
}
}
由上图运行结果,可以看出,当一个操作中多次添加任务的话,系统会开启新的线程,并发执行操作中的任务。
相比NSInvocationOperation,NSBlockOperation多了一个后续持续给该操作添加任务的方法,[op addExecutionBlock:^{ }]
,【注意:针对一个操作,blockOperationWithBlock里的任务和addExecutionBlock里的任务都执行完了,才算该操作执行结束】
3.2、和NSOperationQueue联合使用。
- 3.2.1:添加到主操作队列
- (void)addMainOperationQueue
{
NSLog(@"任务创建:%@",[NSThread currentThread]);
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"1"];
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"2"];
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"3"];
}];
[mainQueue addOperation:op1];
[mainQueue cancelAllOperations];
// [op2 addDependency:op1];
[mainQueue addOperation:op2];
[mainQueue addOperation:op3];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
[op2 addExecutionBlock:^{
[self task:@"2.1"];
}];
[op3 addExecutionBlock:^{
[self task:@"3.1"];
}];
//将操作加入到主队列中后,根据操作添加到队列中的先后顺序(操作之间没有添加依赖关系),串行执行。每个操作addExecutionBlock添加的任务和blockOperationWithBlock中的任务共同组成一个操作。两个block中的操作都执行结束后,一个操作才算结束。
//虽然将操作加到了NSOperationQueue主操作队列,但是当操作中addExecutionBlock加的任务比较多的时候,操作block中的任务会在新的线程中并发执行,但是对于操作来说,操作时串行执行的。
}
- 3.2.2:添加到自定义操作队列
- (void)addCustomOperationQueue
{
NSLog(@"任务创建:%@",[NSThread currentThread]);
NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"1"];
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"2"];
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"3"];
}];
[customQueue addOperation:op1];
[customQueue addOperation:op2];
[customQueue addOperation:op3];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
}
由运行结果可以看出,当将操作添加到自定义队列的话,如果不设置最大并发数maxConcurrentOperationCount的话,操作是并发执行的。当将maxConcurrentOperationCount设置为1的时候,操作串行执行。
3.3、添加依赖。
- (void)addDependenceInCustom
{
NSLog(@"任务创建:%@",[NSThread currentThread]);
NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"1"];
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"2"];
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
[self task:@"3"];
}];
[op1 addDependency:op2];
[op3 addDependency:op1];
[customQueue addOperation:op1];
[customQueue addOperation:op2];
[customQueue addOperation:op3];
[op1 addExecutionBlock:^{
[self task:@"1.1"];
}];
}
NSBlockOperation添加依赖的做法和NSInvocationOperation的写法一样,这里需要注意一下的是:由上图也可以看出结果:op3的执行是在op1完全执行后才执行的。而op1中blockOperationWithBlock和addExecutionBlock这两种block里的任务都执行完,才算op1操作执行结束
4、 自定义Operation的步骤
一般来说,系统提供的两个NSOperation的两个子类NSInvocationOperation
和NSBlockOperation
就能满足开发需求了。但是,如果不想用系统提供的类,可以自定义自己的操作类。继承于NSOperation类。最起码要实现两个方法。
1,初始化对象的方法。
2,重写父类的main
方法,这个方法里是你的任务代码。
- 定义
#import "CustomOperation.h"
@interface CustomOperation()
@property (nonatomic,strong)id data;//作为该操作的参数。
@end
@implementation CustomOperation
- (instancetype)initWithData:(id)data
{
if (self = [super init]) {
self.data = data;
}
return self;
}
- (void)main//只重写了这个方法的话,如果单独手动执行该自定义操作的话,操作时同步执行的,如果操作队列联合起来使用的话,也会并发执行操作。
{
NSString *order = (NSString *)self.data;
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"自定义操作%@----:%@",order,[NSThread currentThread]);
}
}
@end
- 使用
- (void)executeInMain
{
CustomOperation *op = [[CustomOperation alloc]initWithData:@""];
[op start];
}
//和操作队列联合使用
- (void)addMainQueue
{
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
CustomOperation *op1 = [[CustomOperation alloc]initWithData:@"1"];
CustomOperation *op2 = [[CustomOperation alloc]initWithData:@"2"];
CustomOperation *op3 = [[CustomOperation alloc]initWithData:@"3"];
[mainQueue addOperation:op1];
[mainQueue addOperation:op2];
[mainQueue addOperation:op3];
}
- (void)addCustomQueue
{
NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
CustomOperation *op1 = [[CustomOperation alloc]initWithData:@"1"];
CustomOperation *op2 = [[CustomOperation alloc]initWithData:@"2"];
CustomOperation *op3 = [[CustomOperation alloc]initWithData:@"3"];
[customQueue addOperation:op1];
[op1 cancel];
[customQueue addOperation:op2];
[customQueue addOperation:op3];
}
5、 其它
-
NSOperation
的一些常用属性和方法。
//操作是否取消
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;//取消操作
@property (readonly, getter=isExecuting) BOOL executing;//是否执行
@property (readonly, getter=isFinished) BOOL finished;//是否执行结束
@property (readonly, getter=isAsynchronous) BOOL asynchronous //是否异步
@property (readonly, getter=isReady) BOOL ready;//是否处于就绪状态
- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//去除依赖
@property (readonly, copy) NSArray *dependencies;//所有相关依赖
- (void)waitUntilFinished;//执行该操作的时候阻塞当前线程,直到该操作执行
结束。
- (void)addOperationsInCustom
{
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait1"];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait2"];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait3"];
NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait4"];
NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
// [customQueue addOperation:op1];
// [op1 waitUntilFinished];//waitUntilFinished是阻塞当前线程的作用,在这里会阻塞主线程,阻塞主线程中继续往队列中加任务,直到该op1操作执行结束,这样就能实现操作的串行了。
[customQueue addOperation:op2];
[op2 waitUntilFinished];
[customQueue addOperation:op3];
[op3 waitUntilFinished];
[customQueue addOperation:op4];
}
- NSOperationQueue的相关属性和方法
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait ;//可以根据wait参数设置队列中操作的执行方式是串行还是并发。
- (void)addOperationWithBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
@property (readonly) NSUInteger operationCount ;//操作的个数
@property NSInteger maxConcurrentOperationCount;//最大并发数
@property (getter=isSuspended) BOOL suspended;//是否悬挂
- (void)cancelAllOperations;//取消队列中的所有操作。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue;//操作队列对应的GCD队列。
6、 总结
至此;非常感谢您看到这里。
自己总结了一下NSOperation相关的知识点,也又重新理顺了自己的一些想法。但是,这些知识点不用的话,还是会很快忘记的,最好的理解和掌握是在运用在实践中。在以后的实践运用中如果有新内容或新的想法,会持续更新。
文章中的源码已经上传。Demo传送
如有疑问或错误,欢迎指正和提问。谢谢!!!
参考
非常感谢以下文章的作者。谢谢。
iOS多线程:『NSOperation、NSOperationQueue』详尽总结
iOS 并发编程之 Operation Queues