参考博客:NSOperation简介
【iOS】NSOperation, NSOperationQueue
NSOperation的简单介绍
NSOperation 是居于objective-c对GCD封装居于面向对象的,线程执行方式;NSOperation配合NSOperationQueue也能实现多线程编程;
NSOperation和NSOperationQueue实现多线程的具体步骤:
- 先将需要执行的操作封装到一个NSOperation对象中
- 然后将NSOperation对象添加到NSOperationQueue中
- 将取出的NSOperation封装的操作放到一条新线程中执行
NSOperation,NSOperationQueue常用属性和方法归纳
NSOperation常用属性和方法
- 取消操作cancel
- 判断操作状态方法
- isFinished判断操作是否已经结束
- isCancelled判断操作是否取消
- is Excuting判断操作是否正在运行
- isReady判断是否处于就绪状态
- isAsynchronous表示任务是并发还是同步执行
- 操作同步
- waitUntilFinished阻塞当前线程
- setCompletionBlock:(void(^)(void))block;当前操作完毕之后执行block
- addDependency添加依赖
- removeDependency移除依赖
- @property (readonly, copy) NSArray<NSOperation *> *dependencies;数组存储操作
NSOperationQueue的常用属性和方法
- 取消/暂停/恢复队列中的操作
- cancelAllOperations;取消队列中所有的操作
- isSuspended判断队列是否处理暂停状态 YES:暂停状态,NO恢复状态
- setSuspended:(BOOL)b;设置操作的暂停和恢复 YES:暂停,NO:恢复
- 同步操作
- waitUntilAllOperationsAreFinished;阻塞当前线程,直到队列中的操作全部完成
- 添加/获取
- addOperationWithBlock:(void (^)(void))block向队列中添加一个NSBlockOperation类型的操作对象
- addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;添加操作数组,wait标志是否阻塞当前线程知道所有操作结束
- operations当前在队列中的操作数组
- operationCount操作个数
- 获取队列
- currentQueue当前队列,如果当前线程不在Queue上,返回nil
- mainQueue获取主队列
暂停和取消
暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
暂停和取消的区别在于:暂停操作之后还可以恢复操作,而取消操作之后,所有的操作就清空了,无法在接着执行剩下的操作。
NSOperation的使用方法
NSOperation是一个抽象类,本身并不具备封装操作的能力,必须使用其子类。使用NSOperation的子类有两种方式,使用apple提供的现有的子类NSInvocationOperation、NSBlockOperation以及自定义子类继承NSOperation实现内部相应的方法。现简单说明系统提供的子类的使用。
NSInvocationOperation
不配合NSOperationQueue使用的话是在当前线程中串行执行,不会开启新线程。
1.创建操作,封装任务
- 第一个参数:目标对象 self
- 第二个参数:调用方法的名称
- 第三个参数:前面方法需要接受的参数 nil
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:<#(nonnull id)#> selector:<#(nonnull SEL)#> object:<#(nullable id)#>];
2.启动1执行操作
[op1 start];
NSBlockOperation
1.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
/*code*/
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
/*code*/
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
/*code*/
}];
追加任务(开启子线程)
//注意:如果一个操作中的任务量大于1,那么会开子线程并发执行任务
//注意:不一定是子线程,有可能是主线程
[op1 addExecutionBlock:^{
/*code*/
}];
[op2 addExecutionBlock:^{
/*code*/
}];
[op3 addExecutionBlock:^{
/*code*/
}];
2.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
3.添加操作到队列中
[queue addOperation:op1]; // 内部已经调用了[op1 start];
[queue addOperation:op2];
[queue addOperation:op3];
简便方法
1)创建操作
2)添加操作到队列中
[queue addOperationWithBlock^{
<#code#>
}];
使用继承自NSOperation自定义子类
我们可以通过自定义继承自NSOperation的子类,重写main或者start来定义自己的NSOperation对象。
如果只是重写了main方法,有底层控制变更任务执行、完成状态以及任务退出。
如果重写了start方法,需要自己控制任务状态。
重写main方法比较简单,我们不需要管理线程的状态属性executing(是否正在执行)和finished(是否完成)。当main执行完返回的时候,这个操作就结束了。
重写main方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSOperationTest : NSOperation
@end
NS_ASSUME_NONNULL_END
#import "NSOperationTest.h"
@implementation NSOperationTest
- (void)main {
if (!self.isCancelled) {
[NSThread sleepForTimeInterval:2];
NSLog(@"test1--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test2--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test3--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test4--%@", [NSThread currentThread]);
}
}
@end
NSOperationTest* test = [[NSOperationTest alloc] init];
NSLog(@"%d", test.finished);
[test start];
NSLog(@"%d", test.finished);
运行结果:
重写start方法
- (void)start {
if (!self.isCancelled) {
[NSThread sleepForTimeInterval:2];
NSLog(@"test1--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test2--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test3--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test4--%@", [NSThread currentThread]);
}
}
运行结果:
我们明显能看到,重写start方法系统没有自动管理线程的状态属性。
我们没有使用NSOperationQueue,在主线程中创建自定义子类的操作对象并执行,并没有开启新线程。
NSOperationQueue控制串行执行、并行执行
NSOperation操作依赖
NSOperation、NSOperationQueue最吸引人的就是它能够添加操作之间的依赖关系,通过依赖关系,我们就可以很方便的控制操作之间的执行先后顺序。
NSOperation提供了3个接口供我们使用依赖
(void)addDependency:(NSOperation *)op添加依赖,是当前操作依赖op的完成,op完成之后才会执行当前操作。
(void)removeDependency:(NSOperation *)op移除依赖,取消当前操作对操作op的依赖。
@property (readonly, copy) NSArray<NSOperation *> *dependencies;操作对象的一个属性,在当前操作开始执行之前完成执行的所有操作对象数组。
没有添加依赖时:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
没有添加依赖,执行都是并发执行的,没有次序可言。
添加依赖后:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[thirdOperation addDependency:secondOperation]; // 让thirdOperation依赖于secondOperation,即secondOperation先执行,在执行thirdOperation
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
执行有了次序。
互相依赖:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[firstOperation addDependency:secondOperation]; // 让firstOperation依赖于secondOperation,即secondOperation先执行,在执行firstOperation
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
这两个都不执行了。
NSOperation优先级
依赖只是一种执行关系罢了,NSOperation还为我们专门提供了优先级属性,我们可以通过setQueuePriority:方法来设置同一队列中操作的优先级,下面是系统给定的优先级(默认为NSOperationQueuePriorityNormal):
// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
}
对于添加到队列中的操作,首先进入准备就绪的状态,然后进入就绪状态的操作的开始执行顺序由操作之间的相对的优先级决定。
就绪状态取决于操作之间的依赖关系,也就是只有这个操作的依赖操作完成了,该操作才会处于就绪状态。
queuePriority:
- queuePriority属性决定了已进入就绪状态下的操作之间的开始执行顺序。优先级不能取代依赖关系,该属性仅决定开始执行顺序,并不能保证完成执行顺序。
- 如果一个队列中既包含高优先级操作、也有低优先级操作,并且这两个操作都已经准备就绪,那么队列就会先执行高优先级操作。
- queuePriority属性决定的是进入就绪状态下的操作之间的开始执行顺序,并不保证执行完成顺序。而依赖则是控制两个操作之间的执行顺序,使一个操作在它依赖的操作执行完成之后再开始执行。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin firstOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"firstOperation end");
}];
firstOperation.queuePriority = NSOperationQueuePriorityLow;
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin secondOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"secondOperation end");
}];
secondOperation.queuePriority = NSOperationQueuePriorityHigh;
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin thirdOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"thirdOperation end");
}];
thirdOperation.queuePriority = NSOperationQueuePriorityNormal;
queue.maxConcurrentOperationCount = 3;
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
- 如果我们将最大操作执行数设置为1,那么队列中操作数将会按添加的顺序串行执行,一个操作执行完才会执行另一个操作。
- 如果队列中所有的操作的优先级相同,并且也进入就绪状态,那么执行的顺序就按照提交到队列的顺序执行。否则,队列总是执行相对于其他就绪操作优先级更高的操作。
我们还可以通过NSOperation 的qualityOfService属性来设置操作在队列中的服务质量(iOS8 以后苹果推荐使用服务质量替代优先级):
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
}
@property NSQualityOfService qualityOfService;
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务1,%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务2,%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务3,%@",[NSThread currentThread]);
}];
// 设置服务质量
op1.qualityOfService = NSQualityOfServiceBackground;
op2.qualityOfService = NSQualityOfServiceUserInitiated;
op3.qualityOfService = NSQualityOfServiceUtility;
// 将操作加入队列
[queue addOperations:[NSArray arrayWithObjects:op1, op2, op3, nil] waitUntilFinished:YES];
优先级和服务质量都不能绝对保证代码的优先执行情况,这就关系到优先级反转了,可以自己下去了解一下。
总之,设置优先级和服务质量都不能保证代码执行顺序的绝对性,只是改变其先执行或后执行的概率。
如果我们要确保操作的执行的先后顺序,即操作间有同步关系,我们应该使用依赖关系来确保绝对的执行顺序,即使操作对象位于不同的操作队列中。在操作对象的所有依赖操作完成执行之前,操作对象不会被视为已准备好执行。