前言
NSOperation相比GCD的好处有:
1、NSOperationQueue可以方便的调用cancel方法来取消某个操作。
2、NSOperation可以方便的指定操作间的依赖关系。
3、NSOperation可以通过KVO提供对NSOperation对象的精细控制。
4、NSOperation可以方便的指定操作优先级。
5、通过自定义NSOperation的子类可以实现操作重用。
NSOperation
直接上代码NSOperation.h
@interface NSOperation : NSObject {
- (void)start;//启动任务 默认加入到当前队列
- (void)main; //自定义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 NS_AVAILABLE(10_8, 7_0);//是否并发,只读
@property (readonly, getter=isReady) BOOL ready;//准备执行
- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//移除依赖
//所有依赖关系,只读
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
//系统提供的优先级关系枚举
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
//执行优先级
@property NSOperationQueuePriority queuePriority;
//任务执行完成之后的回调
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
//阻塞当前线程,等到某个operation执行完毕。
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
//已废弃,用qualityOfService替代。
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
//任务名称
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
@end
三种创建方法
1、NSBlockOperation
2、NSInvocationOperation
3、NSOperation自定义子类
NSBlockOperation
NSLog(@"currentThread=====%@",[NSThread currentThread]);
//1.封装操作
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//当前线程中执行
NSLog(@"任务1--%@",[NSThread currentThread]);
}];
// 2.1 追加操作,追加的操作在子线程中执行,必须在start前面
// 2.2 但最大并发数为几个(这与dispatch_apply差不多)(包括主线程在内)
[operation addExecutionBlock:^{
NSLog(@"任务2--%@",[NSThread currentThread]);
}];
//3.启动执行操作
[operation start];
/*
NSLog输出信息
currentThread=====<NSThread: 0x60000026e400>{number = 3, name = (null)}
任务1--<NSThread: 0x60000026e400>{number = 3, name = (null)}
任务2--<NSThread: 0x608000266ec0>{number = 4, name = (null)}
*/
NSInvocationOperation
NSLog(@"currentThread=====%@",[NSThread currentThread]);
//1.封装操作
/*
第一个参数:目标对象
第二个参数:该操作要调用的方法,最多接受一个参数
第三个参数:调用方法传递的参数,如果方法不接受参数,那么该值传nil
*/
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(run) object:nil];
//2.启动操作
[operation start];//开始调用run方法,在当前线程执行任务
/*
NSLog输出信息
currentThread=====<NSThread: 0x6000000765c0>{number = 1, name = main}
run=====<NSThread: 0x6000000765c0>{number = 1, name = main}
*/
NSOperation自定义子类
NSOperation有两个方法:main() 和 start()。
1、如果想使用同步,那么最简单方法的就是把逻辑写在main()中,
2、如果想使用异步,需要把逻辑写到start()中,然后加入到队列之中。
//自定义的NSOperation,通过重写内部的main方法实现封装操作
@implementation CustomOperation
- (void)main {
//2秒延时
[NSThread sleepForTimeInterval:2];
NSLog(@"CustomOperation main===%@",[NSThread currentThread]);
}
//使用如下
CustomOperation *operation = [CustomOperation new];
[operation start];
// 同步start,执行完operation的main,operation也dealloc了
使用异步过程中还需要重载一些状态方法(可参考SDWebImageDownloaderOperation),如下
@implementation CustomOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (void)start {
// 就绪开始
[self setExecuting:YES];
// 2秒延时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"=====%@",@"2秒后任务2完成");
[self setExecuting:NO];
[self setFinished:YES];
});
}
#pragma mark - 需要重载的状态方法
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
使用例子
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 任务1
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1====%@",[NSThread currentThread]);
}];
CustomOperation *operation2 = [[CustomOperation new] init];
// 设置任务1依赖任务2,等待任务2完成
[operation1 addDependency:operation2];
[queue addOperation:operation1];
[queue addOperation:operation2];
/*
NSLog输出信息
=====2秒后任务2完成
任务1====<NSThread: 0x60800007f3c0>{number = 4, name = (null)}
*/
各种状态
@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 NS_AVAILABLE(10_8, 7_0);//是否并发,只读
@property (readonly, getter=isReady) BOOL ready;//准备执行
458529-0d86e44aeae2c0d2.png
线程start后并不是立即执行,而是进入一个就绪的状态(isReady),由系统调度执行。
当这几个状态值改变时需要使用KVO通知,其中处于Pending、Ready跟Executing状态的operation是可以被cancel的,而当operation处于finished状态是无法被取消的。当operation成功结束、失败或者被取消了,isFinished的值都会被设置为yes,所以不能仅仅靠isFinished==YES认为operation成功执行。
NSOperation任务执行完成之后的回调completionBlock
operation.completionBlock = ^{
NSLog(@"完成");
};
任务的执行顺序
任务的执行顺序,以下情况都有影响:
1、任务与任务依赖关系:
[operationA addDependency:operationB];
//可以在不同queue的NSOperation之间创建依赖关系
//不要相互依赖,如A依赖B,B依赖A,死锁
// 初始化三个块操作
NSBlockOperation *op1 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"美化 %@", [NSThread currentThread]);
}];
NSBlockOperation *op3 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"更新 %@", [NSThread currentThread]);
}];
// 通过添加依赖可以控制线程执行顺序,依赖关系可以多重依赖
// 注意:不要建立循环依赖,会造成死锁
[op2 addDependency:op1];
[op3 addDependency:op2];
// 直接加到队列里面会并发执行
[self.queue addOperation:op3];
[self.queue addOperation:op1];
[self.queue addOperation:op2];
2、任务的优先级
typedef NS_ENUM(NSInteger, NSQualityOfService) {
// 与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
NSQualityOfServiceUserInteractive = 0x21,
// 在实现用户精确请求请求相关工作时使用UserInitiated QoS,但不要求精确到毫秒,比如动画。例如,如果用户打开email app马上查看邮件。
NSQualityOfServiceUserInitiated = 0x19,
// Utility QoS用于执行已经由用户请求自动发生的任务。例如,电子邮件应用程序可以被配置为每隔5分钟自动检查邮件。如果系统是非常有限的资源,而电子邮件检查被推迟几分钟这也是被允许的。
NSQualityOfServiceUtility = 0x11,
// Background QoS用于执行用户可能甚至都没有意识到正在发生的工作,比如email app可能使用它来执行索引搜索
NSQualityOfServiceBackground = 0x09,
// 优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);
NSOperationQueue
@interface NSOperationQueue : NSObject {
- (void)addOperation:(NSOperation *)op;//添加任务
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加一组任务
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加一个block形式的任务
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;//队列中所有的任务数组
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);//队列中的任务数
@property NSInteger maxConcurrentOperationCount;//最大并发数
@property (getter=isSuspended) BOOL suspended;//暂停
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);//名称
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0); // 底层对应的GCD队列
- (void)cancelAllOperations;//取消队列中的所有任务
- (void)waitUntilAllOperationsAreFinished;//阻塞当前线程,等到队列中的任务全部执行完毕。
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//获取当前队列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//获取主队列
#endif
@end
一般使用
NSLog(@"test start------%@",[NSThread currentThread]);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 任务1
NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[queue addOperation:invocationOper];
// 任务2
NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2------%@",[NSThread currentThread]);
}];
[queue addOperation:blockOper];
// 任务3
[queue addOperationWithBlock:^{
NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
}];
NSBlockOperation *block4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务4------%@",[NSThread currentThread]);
}];
NSBlockOperation *block5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务5------%@",[NSThread currentThread]);
}];
// 任务4、5
[queue addOperations:@[block4,block5] waitUntilFinished:NO];
NSLog(@"test end------%@",[NSThread currentThread]);
/*
NSLog输出信息
test start------<NSThread: 0x60800026fac0>{number = 3, name = (null)}
test end------<NSThread: 0x60800026fac0>{number = 3, name = (null)}
QUEUEBlockOperationRun_<NSThread: 0x600000271800>{number = 4, name = (null)}
任务1------<NSThread: 0x6000002718c0>{number = 5, name = (null)}
任务2------<NSThread: 0x608000270880>{number = 6, name = (null)}
任务4------<NSThread: 0x600000271ac0>{number = 7, name = (null)}
任务5------<NSThread: 0x608000270a00>{number = 8, name = (null)}
*/
队列类型
1、主队列:[NSOperationQueue mainqueue];凡是放在主队列中的操作都在主线程中串行执行
2、非主队列:[[NSOperationQueue alloc]init],并发和串行,默认是并发执行的
最大并发数maxConcurrentOperationCount
1、maxConcurrentOperationCount默认情况下为-1,表示不进行限制,默认为并发执行。
2、maxConcurrentOperationCount大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整。
3、maxConcurrentOperationCount为0,不执行
4、maxConcurrentOperationCount为1时,进行串行执行,但是线程可能是多条(如下代码测试)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
for (int i=0; i< 10; i++) {
[queue addOperationWithBlock:^{
NSLog(@"第%zd个-----%@",i,[NSThread currentThread]);
[NSThread sleepForTimeInterval:0.1];
}];
}
/*
NSLOG输出信息:看出开了2个新线程,按顺序执行
第0个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
第1个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
第2个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
第3个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
第4个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
第5个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
第6个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
第7个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
第8个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
第9个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
*/
暂停和恢复以及取消
//设置暂停和恢复
//suspended设置为YES表示暂停,suspended设置为NO表示恢复
//暂停表示不继续执行队列中的下一个任务,暂停操作是可以恢复的
self.queue.suspended = !self.queue.suspended;
//取消队列里面的所有操作
//取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远都不在执行,就像后面的所有任务都从队列里面移除了一样
//取消操作是不可以恢复的
[self.queue cancelAllOperations];
cancelAllOperations取消队列里面的所有操作
1、移除还没有开始执行的任务,Operation里面还没有执行不需要处理
2、正在执行的任务,Operation的isCancelled为YES,系统不会让我们的Operation自动停止运行代码,但是我们可以监听isCancelled状态,处理自定义Operation里面的逻辑
@implementation CustomOperation
- (void)main {
NSLog(@"CustomOperation start===%@==isCancelled=%zd",[NSThread currentThread],self.isCancelled);
//2秒延时
[NSThread sleepForTimeInterval:2];
NSLog(@"CustomOperation end===%@==isCancelled=%zd",[NSThread currentThread],self.isCancelled);
}
- (void)test{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 任务1
[queue addOperation:[CustomOperation new]];
// 任务2
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
[queue cancelAllOperations];
NSLog(@"CancelAllOperations------%@",[NSThread currentThread]);
}];
/*
NSLog输出信息
CustomOperation start===<NSThread: 0x608000262300>{number = 4, name = (null)}==isCancelled=0
CancelAllOperations------<NSThread: 0x600000263780>{number = 5, name = (null)}
CustomOperation end ===<NSThread: 0x608000262300>{number = 4, name = (null)}==isCancelled=1
*/
}
等待所有操作完成
[queue waitUntilAllOperationsAreFinished];//阻塞当前线程,等到队列中的任务全部执行完毕。
网友评论