1、主要定义

NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。
NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。NSOperation 需要配合 NSOperationQueue 来实现多线程。
NSOperation是一个抽象类,使用前需要子类化(NSInvocationOperation,NSBlockOperation)。NSOperation本身是线程安全的,当我们在子类重写或自定义方法时同样需要保证线程安全。
NSOperation、NSOperationQueue的优势:

  • 可添加完成的代码块,在操作完成后执行。(setCompletionBlock)
  • 添加操作间的依赖关系,方便控制执行顺序。(addDependency)
  • 设定操作执行的优先级。(setQueuePriority)
  • 可以很方便的取消一个操作执行。(- (void)cancel;)
  • 使用KVO观察操作的状态更改。 (isExecuteing,isFinished,isCancelled)

2、单独使用NSOperation

(1)使用NSInvocationOperation子类

代码示例:

  1. - (void)testOpreation {
  2. // 使用子类NSInvocationOperation
  3. NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil];
  4. // 开启操作
  5. [operation start];
  6. }
  7. - (void)doWork {
  8. NSLog(@"dowork---%@", [NSThread currentThread]);
  9. }

打印结果:

dowork—-{number = 1, name = main}

从打印结果中可以看出:在没有使用 NSOperationQueue、在主线程中单独使用使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。

(2)使用NSBlockOperation子类
示例代码:

  1. // 使用子类NSBlockOperation
  2. NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
  3. [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
  4. NSLog(@"block---%@", [NSThread currentThread]);
  5. }];
  6. // 开启执行操作
  7. [blockOperation start];
  8. addExecutionBlock

打印输出:

block—-{number = 1, name = main}

从结果看出和NSInvocationOperation一样的效果。如果在其他线程中开启,则在当前线程中。
但NSBlockOpreation提供了额外的方法addExecutionBlock来添加更多的操作。

  1. // 使用子类NSBlockOperation
  2. NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
  3. [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
  4. NSLog(@"block---%@", [NSThread currentThread]);
  5. }];
  6. //添加额外操作
  7. [blockOperation addExecutionBlock:^{
  8. [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
  9. NSLog(@"block---%@", [NSThread currentThread]);
  10. }];
  11. //添加额外操作
  12. [blockOperation addExecutionBlock:^{
  13. [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
  14. NSLog(@"block---%@", [NSThread currentThread]);
  15. }];
  16. // 开启执行操作
  17. [blockOperation start];

打印结果:

block—-{number = 1, name = main} block—-{number = 5, name = (null)} block—-{number = 6, name = (null)}

使用子类 NSBlockOperation,并调用方法 addExecutionBlock: 的情况下,blockOperationWithBlock:方法中的操作 和 addExecutionBlock: 中的操作是在不同的线程中并发执行的。而且,这次执行结果中 blockOperationWithBlock:方法中的操作也不是在当前线程(主线程)中执行的。从而印证了blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行。

综上:一般情况下,如果一个 NSBlockOperation 对象封装了多个操作。NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。

(3)使用自定义NSOperation子类
自定义继承自 NSOperation 的子类,可以通过重写 main 或者 start 方法 来定义自己的 NSOperation 对象。
示例代码:

  1. @interface SonOperation : NSOperation
  2. @end
  3. @implementation SonOperation
  4. - (void)main {
  5. // 重写main方法
  6. if (!self.isCancelled) {
  7. [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
  8. NSLog(@"SonOperation---%@", [NSThread currentThread]);
  9. }
  10. }
  11. @end

打印结果:

SonOperation—-{number = 1, name = main}

从结果可以看出:在没有使用 NSOperationQueue、在主线程单独使用自定义继承自 NSOperation 的子类的情况下,是在主线程执行操作,并没有开启新线程。

3、使用NSOperationQueue

NSOperationQueue包含主队列和自定义队列。
主队列:(添加到主队列的都在主线程中执行,addExecutionBlock添加的操作可能在其他线程执行)

  1. NSOperationQueue *queue = [NSOperationQueue mainQueue];

自定义队列:

  1. // 自定义操作队列
  2. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  3. // 控制最大并发数
  4. // queue.maxConcurrentOperationCount = 1; // 串型队列
  5. queue.maxConcurrentOperationCount = 2; // 并发队列

最大并发数:maxConcurrentOperationCount (这里控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行)

maxConcurrentOperationCount = 1 则是串型队列 maxConcurrentOperationCount > 1 时则是并发队列,操作并发最大不应该超过系统限制,超过最终也会取系统最大值。 maxConcurrentOperationCount 默认情况下是-1,允许并发控制

设置操作依赖:
通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。
NSOperation 提供了3个接口供我们管理和查看依赖。

    • (void)addDependency:(NSOperation *)op; //添加操作依赖,使当前操作依赖于op操作的完成
    • (void)removeDependency:(NSOperation *)op; // 移除操作依赖,取消当前操作对op操作的依赖
  1. @property (readonly, copy) NSArray *dependencies; // 在当前操作开始执行之前完成的所有操作对象数组。
  1. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  2. // 创建操作A
  3. NSBlockOperation *bOpA = [NSBlockOperation blockOperationWithBlock:^{
  4. NSLog(@"NSBlockOperation A");
  5. }];
  6. NSBlockOperation *bOpB = [NSBlockOperation blockOperationWithBlock:^{
  7. NSLog(@"NSBlockOperation B");
  8. }];
  9. NSBlockOperation *bOpC = [NSBlockOperation blockOperationWithBlock:^{
  10. NSLog(@"NSBlockOperation C");
  11. }];
  12. //设置依赖,先执行C再执行A
  13. [bOpA addDependency:bOpC];
  14. //将操作添加到队列
  15. [queue addOperation:bOpA];
  16. [queue addOperation:bOpB];
  17. [queue addOperation:bOpC];

运行日志:

NSBlockOperation B NSBlockOperation C NSBlockOperation A

可以看出操作A永远在操作C之后执行。
注意⚠️:

设置依赖需要在添加队列之前完成

NSOperation优先级
提供了queuePriority优先级属性,以下是优先级的取值。

  1. typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
  2. NSOperationQueuePriorityVeryLow = -8L,
  3. NSOperationQueuePriorityLow = -4L,
  4. NSOperationQueuePriorityNormal = 0,
  5. NSOperationQueuePriorityHigh = 4,
  6. NSOperationQueuePriorityVeryHigh = 8
  7. };
  8. @property NSOperationQueuePriority queuePriority;

默认情况下,所有操作的默认优先级都是NSOperationQueuePriorityNormal 。我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。
操作设置优先级执行顺序:

  • 当一个操作的所有依赖都已经完成时,操作对象通常会进入准备就绪状态,等待执行。
  • queuePriority 属性决定了进入准备就绪状态下的操作之间的开始执行顺序。并且,优先级不能取代依赖关系。
  • 如果一个队列中既包含高优先级操作,又包含低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。
  • 如果一个队列中既包含了准备就绪状态的操作,又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。那么,虽然准备就绪的操作优先级低,也会优先执行。
  • 优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系。

线程间的通信

  1. NSBlockOperation *bOpA = [NSBlockOperation blockOperationWithBlock:^{
  2. NSLog(@"NSBlockOperation A");
  3. // 线程间的通信
  4. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  5. // 回到主线程
  6. }];
  7. }];