简介
NSOperation
是苹果提供给我们的一套多线程解决方案。实际上NSOperation
是基于GCD
更高一层的封装,但是比GCD
更简单易用、代码可读性也更高。
GCD和NSOperation区别
- 依赖关系
NSOperation
可以设置操作之间的依赖(可以跨队列设置),GCD
无法设置依赖关系,不过可以通过同步来实现这种效果。 - KVO(键值对观察)
NSOperation
容易判断操作当前的状态(是否执行、是否取消等),对此GCD
无法通过KVO
进行判断。 - 优先级
NSOperation
可以设置自身的优先级,但是优先级高的不一定先执行,GCD
只能设置队列的优先级,如果要区分block
任务的优先级,需要很复杂的代码才能实现。 - 继承
NSOperation
是一个抽象类,实际开发中常用的是它的两个子类:NSInvocationOperation
和NSBlockOperation
,同样我们可以自定义NSOperation
,GCD
执行任务可以自由组装,没有继承那么高的代码复用度。 - 效率
直接使用GCD
效率确实会更高效,NSOperation
会多一点开销,但是通过NSOperation
可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己。 - 可操作性
NSOperation
可以随时取消准备执行的任务(已经在执行的不能取消),GCD
没法停止已经加入queue
的block
(虽然也能实现,但是需要很复杂的代码)。
NSOperation使用步骤
NSOperation
单独使用时系统同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue
才能实现异步执行。
1. 创建任务
-
使用子类NSInvocationOperation
/* * target : selector消息发送的对象 * selector : 线程执行的方法,这个selector最多只能接收一个参数 * arg : 传给selector的唯一参数,也可以是nil */ NSInvocationOperation *iOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(iOperationRun) object:nil]; // 执行线程 [iOperation start]; - (void)iOperationRun { NSLog(@"%@", [NSThread currentThread]); }
打印结果:
2018-02-11 18:03:11.375314+0800 ThreadDemo[1459:78875] <NSThread: 0x60400007ec40>{number = 1, name = main}
结论:
在没有使用
NSOperationQueue
、单独使用NSInvocationOperation
的情况下,NSInvocationOperation
在主线程执行操作,并没有开启新线程。 -
使用子类NSBlockOperation
NSBlockOperation *bOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@", [NSThread currentThread]); }]; // 执行线程 [bOperation start];
打印结果:
2018-02-11 18:05:56.397104+0800 ThreadDemo[1492:83479] <NSThread: 0x6000000676c0>{number = 1, name = main}
结论:
在没有使用
NSOperationQueue
、单独使用NSBlockOperation
的情况下,NSBlockOperation
也是在主线程执行操作,并没有开启新线程。但是,
NSBlockOperation
还提供了一个方法addExecutionBlock:
,通过addExecutionBlock:
就可以为NSBlockOperation
添加额外的操作,这些额外的操作就会在其他线程并发执行。- (void)block { NSBlockOperation *bOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"thread 1 : %@", [NSThread currentThread]); }]; [bOperation addExecutionBlock:^{ NSLog(@"thread 2 : %@", [NSThread currentThread]); }]; [bOperation addExecutionBlock:^{ NSLog(@"thread 3 : %@", [NSThread currentThread]); }]; // 执行线程 [bOperation start]; }
打印结果:
2018-02-11 18:12:29.877049+0800 ThreadDemo[1576:94330] thread 2 : <NSThread: 0x600000471600>{number = 4, name = (null)} 2018-02-11 18:12:29.877052+0800 ThreadDemo[1576:94324] thread 3 : <NSThread: 0x604000271a00>{number = 5, name = (null)} 2018-02-11 18:12:29.877051+0800 ThreadDemo[1576:94249] thread 1 : <NSThread: 0x604000073cc0>{number = 1, name = main}
-
定义继承自NSOperation的子类
定义一个继承自NSOperation
的子类,重写main
方法。#import "MyOperation.h" @implementation MyOperation - (void)main { // 新建一个自动释放池,避免内存泄露 @autoreleasepool { NSLog(@"%@", [NSThread currentThread]); } } @end
导入
MyOperation.h
,执行自定义线程。MyOperation *myOp = [[MyOperation alloc] init]; [myOp start];
打印结果:
2018-02-11 18:19:49.814428+0800 ThreadDemo[1658:105849] <NSThread: 0x604000079dc0>{number = 1, name = main}
结论:
在没有使用NSOperationQueue
、单独使用自定义子类的情况下,是在主线程执行操作,并没有开启新线程。
2. 创建队列
NSOperationQueue
一共有两种队列:主队列
、其他队列
。其中其他队列同时包含了串行、并发功能。下边是主队列、其他队列的基本创建方法和特点。
-
主队列
凡是添加到主队列中的任务
NSOperation
,都会放到主线程中执行。NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
-
其他队列
添加到这种队列中的任务NSOperation
,就会自动放到子线程中执行,同时包含了串行、并发功能。NSOperationQueue *queue = [[NSOperationQueue alloc] init];
3. 将任务加入到队列中
-
- (void)addOperation:(NSOperation *)op;
打印结果:NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"thread 1 : %@", [NSThread currentThread]); }]; [queue addOperation:op];
结论:2018-02-11 18:59:27.899345+0800 ThreadDemo[1982:164061] thread 1 : <NSThread: 0x604000276a80>{number = 4, name = (null)}
添加到非主队列的NSOperation
,能够开启新线程,进行并发执行。 -
- (void)addOperationWithBlock:(void (^)(void))block;
打印结果:[queue addOperationWithBlock:^{ NSLog(@"thread 2 : %@", [NSThread currentThread]); }];
结论:2018-02-11 19:04:43.926552+0800 ThreadDemo[2040:171964] thread 2 : <NSThread: 0x600000278dc0>{number = 4, name = (null)}
可以看出NSOperationQueue
的addOperationWithBlock:
能够开启新线程,进行并发执行。
串行执行和并发执行
之前我们说过,NSOperationQueue
创建的其他队列同时具有串行、并发功能,那么怎么去设置串行和并发呢?通过设置最大并发数maxConcurrentOperationCount
来实现串行和并发。
-
maxConcurrentOperationCount
默认情况下为-1,表示不进行限制,默认为并发执行。 - 当
maxConcurrentOperationCount
为1时,进行串行执行。 - 当
maxConcurrentOperationCount
大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
NSLog(@"thread 1 : %@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"thread 2 : %@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"thread 3 : %@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"thread 4 : %@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"thread 5 : %@", [NSThread currentThread]);
}];
打印结果:
2018-02-11 19:17:03.033446+0800 ThreadDemo[2148:190380] thread 1 : <NSThread: 0x60400027e980>{number = 4, name = (null)}
2018-02-11 19:17:03.033766+0800 ThreadDemo[2148:190380] thread 2 : <NSThread: 0x60400027e980>{number = 4, name = (null)}
2018-02-11 19:17:03.034790+0800 ThreadDemo[2148:190380] thread 3 : <NSThread: 0x60400027e980>{number = 4, name = (null)}
2018-02-11 19:17:03.035108+0800 ThreadDemo[2148:190380] thread 4 : <NSThread: 0x60400027e980>{number = 4, name = (null)}
2018-02-11 19:17:03.036011+0800 ThreadDemo[2148:190380] thread 5 : <NSThread: 0x60400027e980>{number = 4, name = (null)}
queue.maxConcurrentOperationCount = 2;
打印结果:
2018-02-11 19:20:45.667381+0800 ThreadDemo[2211:197053] thread 2 : <NSThread: 0x60400047ae40>{number = 5, name = (null)}
2018-02-11 19:20:45.667436+0800 ThreadDemo[2211:197054] thread 1 : <NSThread: 0x60000026ca40>{number = 4, name = (null)}
2018-02-11 19:20:45.667700+0800 ThreadDemo[2211:197054] thread 4 : <NSThread: 0x60000026ca40>{number = 4, name = (null)}
2018-02-11 19:20:45.667705+0800 ThreadDemo[2211:197055] thread 3 : <NSThread: 0x60000026d0c0>{number = 6, name = (null)}
2018-02-11 19:20:45.668231+0800 ThreadDemo[2211:197054] thread 5 : <NSThread: 0x60000026ca40>{number = 4, name = (null)}
结论:
当最大并发数为1时,任务是按顺序串行执行的。
当最大并发数为2时,任务是并发执行的。而且开启线程数量是由系统决定的,不需要我们来管理。这样看来,是不是比GCD还要简单了许多?
操作依赖
NSOperation
和NSOperationQueue
最吸引人的地方是它能添加操作之间的依赖关系。比如说有A、B两个操作,其中A执行完操作,B才能执行操作,那么就需要让B依赖于A。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thread 1 : %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thread 2 : %@", [NSThread currentThread]);
}];
// op1依赖op2完成才执行
[op1 addDependency:op2];
[queue addOperation:op1];
[queue addOperation:op2];
打印结果:
2018-02-11 19:58:01.051917+0800 ThreadDemo[2505:220669] thread 2 : <NSThread: 0x60000027f600>{number = 4, name = (null)}
2018-02-11 19:58:01.052233+0800 ThreadDemo[2505:220670] thread 1 : <NSThread: 0x604000473140>{number = 5, name = (null)}
注意:
依赖关系可以跨队列,NSOperation
之间的相互依赖会导致死锁。
其他方法
- 取消任务
// 取消任务(NSOperation) - (void)cancel; // 取消剩余未执行的任务(NSOperationQueue) - (void)cancelAllOperations;
- 暂停与恢复
// YES 暂停,NO 恢复(NSOperation) @property (getter=isSuspended) BOOL suspended;
- 优先级
// (NSOperation) @property NSOperationQueuePriority queuePriority; typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8 };
- 完成块
// Operation完成后调用的代码块 @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
- 获取状态
// 是否取消 @property (readonly, getter=isCancelled) BOOL cancelled; // 是否执行完成 @property (readonly, getter=isFinished) BOOL finished; // 是否正在执行 @property (readonly, getter=isExecuting) BOOL executing; // 是否异步执行 @property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0); // 在start方法开始之前,需要确定Operation是否准备好,默认为YES,如果该operation没有准备好,则不会start。 @property (readonly, getter=isReady) BOOL ready; // 获取有依赖关系的Operation所组成的数组 @property (readonly, copy) NSArray<NSOperation *> *dependencies; // NSOperationQueue // 任务个数 @property (readonly) NSUInteger operationCount; // 所有任务 @property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
- QOS
iOS8
之后提供的新功能,苹果提供了几个Quality of Service
枚举来使用。通过这些枚举告诉系统我们在进行什么样的工作,然后系统会通过合理的资源控制来最高效的执行任务代码,其中主要涉及到CPU
调度的优先级、IO
优先级、任务运行在哪个线程以及运行的顺序等等,我们可以通过一个抽象的Quality of Service
枚举参数来表明任务的意图以及类别。@property NSQualityOfService qualityOfService; // NSQualityOfServiceUserInteractive // 与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成. // NSQualityOfServiceUserInitiated // 由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成 // NSQualityOfServiceUtility // 一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间 // NSQualityOfServiceBackground // 这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时 // NSQualityOfServiceDefault // 优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
- 其他
// 任务名字 @property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0); // 是否等待Operation执行结束才执行后面的代码。如果为YES,会堵塞当前线程,直到Operation执行结束,才会执行接下来的代码 - (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0); // NSOperationQueue // 队列名字 @property (nullable, copy) NSString *name; // 阻塞当前线程,等待所有操作执行完毕 - (void)waitUntilAllOperationsAreFinished;
网友评论