>

实际上NSOperation是基于GCD更高一层的封装,必须使

- 编辑:澳门博发娱乐官网 -

实际上NSOperation是基于GCD更高一层的封装,必须使

  • 配合使用NSOperation和NSOperationQueue也能实现多线程编程
    • NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
      • NSInvocationOperation
      • NSBlockOperation
      • 自定义子类继承NSOperation,实现内部相应的main方法
    • NSOperationQueue的作用
      • NSOperation可以调用start方法来执行任务,但默认是同步执行的
      • 如果将NSOperation添加到NSOperationQueue中,系统会自动异步执行NSOperation中的操作
        • 添加方法1:- addOperation:(NSOperation *)op;
        • 添加方法2:- addOperationWithBlock:block;
  • 使用NSInvocationOperation实现多线程编程
    • 步骤:
      • 封装操作 initWithTarget
      • 实现操作对应的方法 sel
      • 启动操作 start
    • 注意
      • 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
      • 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
  • 使用NSBlockOperation实现多线程编程
    • 步骤:
      • 封装操作 blockOperationWithBlock
      • 追加操作 addExecutionBlock:(此步骤可以省略,即可以追加操作也可以不追加操作)
      • 启动操作 start
    • 注意
      • 只要NSBlockOperation封装的操作数>1,就会异步执行操作
      • 即只有追加操作,才会异步执行操作
  • 自定义子类继承NSOperation,实现内部相应的main方法
    • 创建自定义的子类继承NSOperation
    • 在子类中实现内部相应的main方法
      • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
      • 经常通过-isCancelled方法检测操作是否被取消,对取消做出响应
    • 实例化子类对象,并实现操作
  • 使用NSInvocationOperation和NSOperationQueue实现多线程编程
    • 步骤
      • 封装操作
      • 创建队列
        • 主队列mainQueue(凡是在主队列中的任务都在主线程中执行&串行队列)
        • 非主队列[[NSOperationQueue alloc]init](同时具备了并发和串行的功能,默认是并发队列)
      • 把操作添加到队列中
        • addOperation:
        • addOperationWithBlock:
  • 并发数

    • 概念:同时执行的任务数
    • 必须在操作添加到队列之前设置最大并发数的个数
    • 最大并发数的方法

        • (NSInteger)maxConcurrentOperationCount;
        • setMaxConcurrentOperationCount:(NSInteger)cnt;
    • 最大并发数量

      • maxConcurrentOperationCount = 1 串行队列
      • maxConcurrentOperationCount > 1 并发队列
      • maxConcurrentOperationCount = -1 并发数不做限制,并发队列
      • maxConcurrentOperationCount = 0 程序不能运行
  • 同步执行任务的情况
    • 仅使用NSInvocationOperation
    • 仅使用NSBlockOperation
    • 使用NSOperation和mainQueue
    • 使用NSOperation和[[NSOperationQueue alloc]init]且最大并发数为0
  • 队列的暂停、恢复、取消
    • 队列的暂停
      • -setSuspended:b; // YES代表暂停队列,NO代表恢复队列
      • -isSuspended;
      • 不能暂停当前处于执行状态的操作
      • 只能停止后面没有处于执行状态的操作
      • 暂停操作是可以恢复的
    • 队列的恢复
      • -setSuspended:b; // YES代表暂停队列,NO代表恢复队列
      • -isSuspended;
    • 队列的取消
      • 取消所有队列的操作-cancelAllOperations;,内部调用每个操作的cancel方法
      • 调用NSOperation的- cancel方法取消单个操作
      • 后面的任务将永远没有机会执行
      • 取消操作是不能恢复的
    • 实现步骤
      • 在storyboard中拖拽开始执行、暂停、取消、恢复4个按钮
      • 生成其对应的点击事件的方法
      • 开始执行方法
        • 使用NSBlockOperation封装操作
        • 创建队列
        • 设置最大并发数为1
        • 把操作添加到队列
      • 暂停方法
        • 设置isSuspended属性为YES
      • 取消方法
        • 调用cancelAllOperations方法
      • 恢复方法
        • 设置isSuspended属性为NO
    • 实现代码

一、简介

  • 除了,NSThread和GCD实现多线程,配合使用NSOperation和NSOperationQueue也能实现多线程编程

NSOperation和NSOperationQueue实现多线程的具体步骤

  • 1、先将需要执行的操作封装到一个NSOperation的子类对象中
    • 实际上,NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
  • 2、然后将NSOperation对象添加到NSOperationQueue中
  • 3、系统会自动将NSOperationQueue中的NSOperation取出来
  • 4、将取出的NSOperation封装的操作放到一条新线程中执行

简介

一.NSOperation简介

一、什么是NSOperation?

      NSOperation是苹果提供的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更加的面向对象、代码可读性更高、可控性更强,很屌的是加入了操作依赖。

      默认情况下,NSOperation单独使用时只能同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步执行。讲到这里,我们不难发现GCD和NSOperation实现的方式很像,其实这更像是废话,NSOperation本身就是基于GCD的封装,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列,前面《iOS多线程开发之GCD(上篇)》中已经阐述过GCD的实质:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。这样我们也可说NSOperation的本质就是:定义想执行的任务(NSOperation)并追加到适当的NSOperationQueue中。

 

二、NSOperation

  • 如上所示:要实现多线程,必须要将执行的操作封装到NSOperation的子类对象中,那么NSOperation的子类有哪些?

NSOperation的作用

1.什么是NSOperation?

二、NSOperation使用

     1、创建任务

     NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。但它不能直接用来封装任务,只能通过它的子类来封装,一般的我们可以使用:NSBlockOperation、NSInvocationOperation或者定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。

   (1)NSInvocationOperation

- (void)invocationOperation{

    NSLog(@"start - %@",[NSThread currentThread]);

    // 创建NSInvocationOperation对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil];

    // 调用start方法开始执行操作
    [op start];

    NSLog(@"end - %@",[NSThread currentThread]);
}

- (void)testRun{
    NSLog(@"invocationOperation -- %@", [NSThread currentThread]);
}

    执行结果:

2017-07-14 13:43:59.327 beck.wang[10248:1471363] start - <NSThread: 0x6100000614c0>{number = 1, name = main}
2017-07-14 13:43:59.328 beck.wang[10248:1471363] invocationOperation -- <NSThread: 0x6100000614c0>{number = 1, name = main}
2017-07-14 13:43:59.328 beck.wang[10248:1471363] end - <NSThread: 0x6100000614c0>{number = 1, name = main}

    分析:单独使用NSInvocationOperation的情况下,NSInvocationOperation在主线程同步执行操作,并没有开启新线程。

  (2)NSBlockOperation

- (void)blockOperation{

    NSLog(@"start - %@",[NSThread currentThread]);

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation--%@", [NSThread currentThread]);
    }];

    NSLog(@"end - %@",[NSThread currentThread]);

    [op start];
}

    打印结果:

2017-07-14 13:49:25.436 beck.wang[10304:1476355] start - <NSThread: 0x6100000653c0>{number = 1, name = main}
2017-07-14 13:49:25.436 beck.wang[10304:1476355] end - <NSThread: 0x6100000653c0>{number = 1, name = main}
2017-07-14 13:49:25.436 beck.wang[10304:1476355] blockOperation--<NSThread: 0x6100000653c0>{number = 1, name = main}

    分析:单独使用NSBlockOperation的情况下,NSBlockOperation也是在主线程执行操作,没有开启新线程。

    值得注意的是:NSBlockOperation还提供了一个方法addExecutionBlock:,通过addExecutionBlock:就可以为NSBlockOperation添加额外的操作,这些额外的操作就会在其他线程并发执行。

- (void)blockOperation{

    NSLog(@"start - %@",[NSThread currentThread]);

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation--%@", [NSThread currentThread]);
    }];

    // 添加额外任务(在子线程执行)
    [op addExecutionBlock:^{
        NSLog(@"addTask1---%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"addTask2---%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"addTask3---%@", [NSThread currentThread]);
    }];

    NSLog(@"end - %@",[NSThread currentThread]);

    [op start];
}

     打印结果:

2017-07-14 13:57:02.009 beck.wang[10351:1482603] start - <NSThread: 0x60000007cdc0>{number = 1, name = main}
2017-07-14 13:57:02.009 beck.wang[10351:1482603] end - <NSThread: 0x60000007cdc0>{number = 1, name = main}
2017-07-14 13:57:02.010 beck.wang[10351:1482603] blockOperation--<NSThread: 0x60000007cdc0>{number = 1, name = main}
2017-07-14 13:57:02.010 beck.wang[10351:1482642] addTask1---<NSThread: 0x618000260e00>{number = 3, name = (null)}
2017-07-14 13:57:02.010 beck.wang[10351:1482645] addTask3---<NSThread: 0x600000263200>{number = 5, name = (null)}
2017-07-14 13:57:02.010 beck.wang[10351:1482643] addTask2---<NSThread: 0x610000264600>{number = 4, name = (null)}

    分析:blockOperationWithBlock任务在主线程中执行,addExecutionBlock的任务在新开线程中执行。

   

    (3)自定义NSOperation子类--重写main方法即可

    .h

@interface ZTOperation : NSOperation

@end

    .m

@implementation ZTOperation

- (void)main{

    // 在这里可以自定义任务
    NSLog(@"ZTOperation--%@",[NSThread currentThread]);
}
@end

    ViewController

ZTOperation *zt = [[ZTOperation alloc] init];
[zt start];

    打印结果:

2017-07-14 14:05:58.824 beck.wang[10389:1490955] ZTOperation--<NSThread: 0x60000007a940>{number = 1, name = main}

    分析:任务在主线程中执行,不开启新线程。

 

    2、创建队列

    NSOperationQueue一共有两种队列:主队列、其他队列。其中其他队列同时包含了串行、并发功能,通过设置最大并发数maxConcurrentOperationCount来实现串行、并发!

  (1)主队列  -- 任务在主线程中执行

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

    (2)其他队列 -- 任务在子线程中执行

NSOperationQueue *elseQueue = [[NSOperationQueue alloc] init];

 

    3、NSOperation  +  NSOperationQueue (任务追加到队列)

// 添加单个操作:
 - (void)addOperation:(NSOperation *)op;

// 添加多个操作:
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);

// 添加block操作:
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

      代码示例:

- (void)addOperationToQueue
{

    NSLog(@"start - %@",[NSThread currentThread]);

    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 创建NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil];

    // 创建NSBlockOperation
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task002 -- %@", [NSThread currentThread]);
    }];

    // 添加操作到队列中: addOperation:
    [queue addOperation:op1];
    [queue addOperation:op2];

    // 添加操作到队列中:addOperationWithBlock:
    [queue addOperationWithBlock:^{
        NSLog(@"task003-----%@", [NSThread currentThread]);
    }];

    NSLog(@"end - %@",[NSThread currentThread]);
}

- (void)testRun{
    NSLog(@"task001 -- %@", [NSThread currentThread]);
}

     打印结果:

2017-07-14 14:39:51.669 beck.wang[10536:1516641] start - <NSThread: 0x610000077640>{number = 1, name = main}
2017-07-14 14:39:51.670 beck.wang[10536:1516641] end - <NSThread: 0x610000077640>{number = 1, name = main}
2017-07-14 14:39:51.670 beck.wang[10536:1516686] task003-----<NSThread: 0x600000077200>{number = 3, name = (null)}
2017-07-14 14:39:51.670 beck.wang[10536:1516689] task002 -- <NSThread: 0x61800007e080>{number = 5, name = (null)}
2017-07-14 14:39:51.670 beck.wang[10536:1516687] task001 -- <NSThread: 0x61000007e1c0>{number = 4, name = (null)}

    分析:开启新线程,并发执行。

 

@interface ViewController ()/** 队列 */@property(nonatomic, strong)NSOperationQueue *queue;@end@implementation ViewController/** * 开始执行 */- startBtnClick { // 创建操作对象 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 10000; i++) { NSLog(@"1---%zd---main------%@", i,[NSThread currentThread]); } }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 10000; i++) { NSLog(@"2---%zd---main------%@", i,[NSThread currentThread]); } }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 10000; i++) { NSLog(@"3---%zd---main------%@", i,[NSThread currentThread]); } }]; // 创建队列 self.queue = [[NSOperationQueue alloc] init]; // 设置最大并发数 self.queue.maxConcurrentOperationCount = 1; // 把操作添加到队列 [self.queue addOperation:op1]; [self.queue addOperation:op2]; [self.queue addOperation:op3];}/** * 暂停 */- suspendBtnClick { [self.queue setSuspended:YES];}/** * 取消 */- cancleBtnClick { [self.queue cancelAllOperations];}/** * 恢复 */- resumeBtnClick { [self.queue setSuspended:NO];}@end
1、使用NSOperation子类的方式有3种
  • NSInvocationOperation
  • NSBlockOperation
  • 自定义子类继承NSOperation,实现内部相应main的方法封装操作

1.1 NSInvocationOperation

  • 创建NSInvocationOperation对象
-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)arg;
  • 调用start方法开始执行操作
    • 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
// 一旦执行操作,就会调用target的selector方法
-(void)start;

  + 只有将NSOperation操作任务放到一个NSOperationQueue中,才会异步执行操作
  • 使用
- (void)invocation
{    
    // 1.将操作封装到Operation中
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
    // 2.执行封装的操作
    // 如果直接执行NSInvocationOperation中的操作, 那么默认会在主线程中执行
    [op1 start];

    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
    [op2 start];
}

1.2 NSBlockOperation

  • 注意点:只要NSBlockOperation封装的操作数 >1,就会异步执行操作

  • 创建NSBlockOperation对象

+(id)blockOperationWithBlock:(void(^)(void))block;
  • 通过addExecutionBlock:方法添加更多的操作
-(void)addExecutionBlock:(void(^)(void))block;
  • 使用
- (void)blockOperation
{
    // 1.封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1- %@", [NSThread currentThread]);
    }];

    // 2.添加操作
    [op1 addExecutionBlock:^{
        NSLog(@"2- %@", [NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        NSLog(@"3- %@", [NSThread currentThread]);
    }];

    // 2.执行操作
    // 如果只封装了一个操作, 那么默认会在主线程中执行
    // 如果封装了多个操作, 那么除了第一个操作以外, 其它的操作会在子线程中执行
    [op1 start];
}

1.3 自定义 NSOperation,继承NSOperation

  • 如果是自定义类继承于NSOperation, 那么需要将操作写到自定义类的main方法中,重写main方法

    • 重写-(void)main方法的注意点
      • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
      • 经常通过-(BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
  • 这种实现方式封装操作, 可以提高代码的复用性

  • 1.创建类JPOperation,继承NSOperation

#import <Foundation/Foundation.h>
@interface JPOperation : NSOperation

@end

#import "JPOperation.h"

@implementation JPOperation

// 我们要重写main方法,封装操作
- (void)main
{
    NSLog(@"%s, %@", __func__,[NSThread currentThread]);
}
@end
  • 2.使用自定义的NSOperation
    // 1.封装操作
    JPOperation *op1 = [[JPOperation alloc] init];
    // 2.执行操作
    [op1 start];

    JPOperation *op2 = [[JPOperation alloc] init];
    [op2 start];

配合使用NSOperation和NSOperationQueue也能实现多线程编程

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。

三、NSOperationQueue管理

     1、队列的取消、暂停、恢复

         - (void)cancel;                           NSOperation提供的方法,可取消单个操作

         - (void)cancelAllOperations;         NSOperationQueue提供的方法,可以取消队列的所有操作

         - (void)setSuspended:(BOOL)b;    可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列

         - (BOOL)isSuspended;                判断暂停状态

         暂停或取消并不能使正在执行的操作立即暂停或取消,而是当前操作执行完后不再执行新的操作。两者的区别在于暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

 

     2、最大并发数 maxConcurrentOperationCount

          maxConcurrentOperationCount = - 1  表示不限制,默认并发执行;

          maxConcurrentOperationCount = 1 表示最大并发数为1,串行执行;

          maxConcurrentOperationCount > ([count] > =1)  表示并发执行,min[count,系统限制]。

         代码示例:

- (void)operationQueue
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 设置最大并发操作数
    // queue.maxConcurrentOperationCount = - 1;  // 并发执行
    // queue.maxConcurrentOperationCount = 1; // 同步执行
     queue.maxConcurrentOperationCount = 2; // 并发执行

    [queue addOperationWithBlock:^{
        NSLog(@"task1-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task3-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task4-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task5-----%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"task6-----%@", [NSThread currentThread]);
    }];
}

     打印结果:

// queue.maxConcurrentOperationCount = - 1

2017-07-14 15:28:39.554 beck.wang[10772:1557342] task2-----<NSThread: 0x61800006d340>{number = 4, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557358] task3-----<NSThread: 0x6080000751c0>{number = 5, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557359] task4-----<NSThread: 0x610000071c00>{number = 6, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557339] task5-----<NSThread: 0x60000006ea40>{number = 7, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557340] task1-----<NSThread: 0x608000073500>{number = 3, name = (null)}
2017-07-14 15:28:39.554 beck.wang[10772:1557360] task6-----<NSThread: 0x610000071c80>{number = 8, name = (null)}

// 分析:线程数为6,并发执行

-----------------------------------分割线----------------------------------------------

// queue.maxConcurrentOperationCount =  1

2017-07-14 15:27:04.365 beck.wang[10743:1555231] task1-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.365 beck.wang[10743:1555231] task2-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.365 beck.wang[10743:1555231] task3-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.365 beck.wang[10743:1555231] task4-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.366 beck.wang[10743:1555231] task5-----<NSThread: 0x60800007c880>{number = 3, name = (null)}
2017-07-14 15:27:04.366 beck.wang[10743:1555231] task6-----<NSThread: 0x60800007c880>{number = 3, name = (null)}

// 分析:线程个数为1,同步执行

-----------------------------------分割线----------------------------------------------

// queue.maxConcurrentOperationCount =  2

2017-07-14 15:18:26.162 beck.wang[10715:1548342] task2-----<NSThread: 0x608000079740>{number = 4, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548344] task1-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548342] task4-----<NSThread: 0x608000079740>{number = 4, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548344] task3-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}
2017-07-14 15:18:26.162 beck.wang[10715:1548342] task5-----<NSThread: 0x608000079740>{number = 4, name = (null)}
2017-07-14 15:18:26.163 beck.wang[10715:1548344] task6-----<NSThread: 0x6100000770c0>{number = 3, name = (null)}

// 分析:线程个数为2,并发执行

     很明显,通过设置maxConcurrentOperationCount就能实现并发、串行功能是不是比GCD轻松多了!

     

     3、操作依赖

      NSOperation中我们可以为操作分解为若干个小的任务,通过添加他们之间的依赖关系进行操作,这个经常用到!这也是NSOperation吸引人的地方,不需要像GCD那样使用复杂的代码实现,addDependency就可以搞定!

- (void)addDependency
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"task1-----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task3-----%@", [NSThread  currentThread]);
    }];

    // op2依赖于op1 执行顺序op1->op2 必须放在[添加操作队列]之前
    [op2 addDependency:op1];

    // 忌循环依赖 op2已经依赖于op1,切不可再让op1依赖于op2,形成循环依赖
    //[op1 addDependency:op2];

    // 添加操作队列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}

     打印结果:

2017-07-14 15:46:02.011 beck.wang[10854:1571574] task3-----<NSThread: 0x61800006d740>{number = 3, name = (null)}
2017-07-14 15:46:04.085 beck.wang[10854:1571596] task1-----<NSThread: 0x60000006f040>{number = 4, name = (null)}
2017-07-14 15:46:04.085 beck.wang[10854:1571574] task2-----<NSThread: 0x61800006d740>{number = 3, name = (null)}

    分析:task2一定在task1后面执行,因为执行task1前设置了线程等待2s,所有task3最早执行。

 

    4、操作优先级

NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8

   

    5、操作的监听

       可以监听一个操作是否执行完毕,如下载图片,需要在下载第一张图片后才能下载第二张图片,这里就可以设置监听。

- (void)addListing{

    NSOperationQueue *queue=[[NSOperationQueue alloc]init];

    NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
        for (int i=0; i<3; i++) {
            NSLog(@"下载图片1-%@",[NSThread currentThread]);
        }
    }];

    // 监听操作的执行完毕
    operation.completionBlock=^{
        // 继续进行下载图片操作
        NSLog(@"--下载图片2--");
    };

    [queue addOperation:operation];
}

     执行结果:

2017-07-14 16:21:43.833 beck.wang[10930:1597954] 下载图片1-<NSThread: 0x61800007a340>{number = 3, name = (null)}
2017-07-14 16:21:43.834 beck.wang[10930:1597954] 下载图片1-<NSThread: 0x61800007a340>{number = 3, name = (null)}
2017-07-14 16:21:43.834 beck.wang[10930:1597954] 下载图片1-<NSThread: 0x61800007a340>{number = 3, name = (null)}
2017-07-14 16:21:43.834 beck.wang[10930:1597955] --下载图片2--

    分析:下载图片1完成后才会执行下载图片2,这里类似知识点3中的添加依赖。

 

    留在最后的话:多线程不只是有GCD!如果你还没有用过NSOperation,还说什么呢?赶紧操练起来!当然他们各有各的使用场景,存在即合理!iOS多线程的三种技术GCD、NSThread、NSOperation就都介绍完了,需要了解 GCD、NSThread的可以回头看看我之前的博客。 

   

  • 自定义NSOperatioin子类实现队列的取消
    • 自定义NSOperatioin子类
    • 重写main方法
    • 在storyboard中拖拽开始执行、取消2个按钮
      • 生成其对应的点击事件的方法
      • 开始执行方法
        • 使用自定义NSOperatioin子类封装操作
        • 创建队列
        • 设置最大并发数为1
        • 把操作添加到队列
      • 取消方法
        • 调用cancelAllOperations方法
    • 代码实现

三、NSOperationQueue

  • NSOperationQueue的作用:

    • 如上所述:NSOperation可以调用start方法来执行任务,但默认是同步执行的
    • 如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
  • 添加操作到NSOperationQueue:两个方法

    • 只要将一个任务添加到alloc/init的队列(默认并发,可以设置其为串行)中, 那么队列内部会自动调用start
    • 如果想实现串行, 那么就设置队列的maxConcurrentOperationCount = 1
-(void)addOperation:(NSOperation*)op;
-(void)addOperationWithBlock:(void(^)(void))block;

基本使用

  • GCD队列:

    • 串行: 自己创建, 主队列
    • 并发: 自己创建, 全局
  • NSOperationQueue:

    • 自己创建: alloc/init --> 默认是并发 --> 也可以让它串行
    • 主队列 : mainQueue
#import "ViewController.h"
#import "JPOperation.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 2.封装任务
    JPOperation *op1 = [[JPOperation alloc] init];
    JPOperation *op2 = [[JPOperation alloc] init];

    // 3.将任务添加到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

- (void)block
{
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.将任务添加到队列中
    // addOperationWithBlock方法会做两件事情
    // 1.根据传入的block, 创建一个NSBlockOperation对象
    // 2.将内部创建好的NSBlockOperation对象, 添加到队列中
    [queue addOperationWithBlock:^{
        NSLog(@"1 = %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"2 = %@", [NSThread currentThread]);
    }];
}

- (void)invation
{
    /*
     GCD队列:
     串行: 自己创建, 主队列
     并发: 自己创建, 全局

     NSOperationQueue:
     自己创建: alloc/init --> 默认是并发 --> 也可以让它串行
     主队列  : mainQueue
     */
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //    NSOperationQueue *queue = [NSOperationQueue mainQueue];

    // 2.封装任务
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];

    // 3.将任务添加到队列中
    // 只要将一个任务添加到alloc/init的队列中, 那么队列内部会自动调用start
    // 只要将一个任务添加到alloc/init的队列中, 就会开启一个新的线程执行队列
    [queue addOperation:op1];
    [queue addOperation:op2];
}

- (void)demo
{
    NSLog(@"demo = %@", [NSThread currentThread]);
}
- (void)test
{
    NSLog(@"test = %@", [NSThread currentThread]);
}
@end

NSOperationNSOperationQueue实现多线程的具体步骤

2.NSOperation的作用

四、NSOperatinoQueue的串行和并发 : 最大并发数

  • 队列的maxConcurrentOperationCount最大并发数

  • maxConcurrentOperationCount 默认等于 -1, 代表不限制, 可以创建N多线程

  • alloc/init的NSOperatinoQueue队列默认就是并发, 如果想实现串行, 那么就设置maxConcurrentOperationCount = 1

  • 注意: 最大并发数, 不能设置为0, 否则任务不会被执行

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init
                               ];
    // maxConcurrentOperationCount 默认等于 -1, 代表不限制, 可以创建N多线程
    // 默认就是并发
    // 如果想实现串行, 那么就设置maxConcurrentOperationCount = 1
    // 注意: 最大并发数, 不能设置为0, 否则任务不会被执行 
    如果想再主线程中执行任务, 那么直接创建mainQueu即可
//    queue.maxConcurrentOperationCount = 1;

    // 2.创建任务
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"1 = %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"2 = %@", [NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"3 = %@", [NSThread currentThread]);
    }];

    // 3.将任务添加到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];   
}
@end

先将需要执行的操作封装到一个NSOperation对象中

配合使用NSOperation和NSOperationQueue也能实现多线程编程

// 控制器代码@interface ViewController ()/** 队列 */@property(nonatomic, strong)NSOperationQueue *queue;@end@implementation ViewController- startBtnClick { // 封装操作 ZQOperation *op = [[ZQOperation alloc] init]; // 创建队列 self.queue = [[NSOperationQueue alloc] init]; // 设置最大并发数 self.queue.maxConcurrentOperationCount = 1; // 把操作添加到队列 [self.queue addOperation:op];}- cancelBtnClick { [self.queue cancelAllOperations];}@end

五、NSOperationQueue的暂停-恢复-取消

1、取消队列的所有操作

  • 也可以调用NSOperation的-(void)cancel方法取消单个操作

  • 注意点:

    • 任务只要被取消, 就不会再恢复了
    • 取消任务和暂停任务一样, 不会取消当前正在执行的任务, 只能取消还未执行的任务
-(void)cancelAllOperations;

2、暂停和恢复队列

  • 注意:
    • 1.如果在任务执行的过程中暂停队列中的任务, 那么当前正在执行的任务并不会被暂停, 而是会暂停队列中的下一个任务
    • 2.恢复任务, 是从队列第一个没有被执行过的任务开始恢复
-(void)setSuspended:(BOOL)b;//如果是YES, 代表需要暂停,NO代表代表不需要暂停 ==  恢复执行
-(BOOL)isSuspended;

然后将NSOperation对象添加到NSOperationQueue

3.NSOperation和NSOperationQueue实现多线程的具体步骤

图片 1

六、NSOperationQueue线程间通信

实例程序:开启子线程下载图片,下载好图片后,回到主线程进行更新UI

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 1.开启子线程下载图片
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
     // 子线程
        NSString *urlStr = @"https://www.baidu.com/img/bd_logo1.png";
    // url中文编码,防止乱码
    // urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlStr];
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 2.生成下载好的图片
        UIImage *image = [UIImage imageWithData:data];

        // 3.回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"更新UI");
           // 主线程
            self.imageView.image = image;
        }];
    }];
}
@end

系统会自动将NSOperationQueue中的NSOperation取出来

先将需要执行的操作封装到一个NSOperation对象中

  • 操作的依赖、监听

    • 操作的依赖
      • NSOperation之间可以设置依赖来保证执行顺序
      • 可以在不同queue的NSOperation之间创建依赖关系
      • [operationB addDependency:operationA]; // 操作B依赖于操作A
      • 不能设置循环依赖:A依赖B,B依赖A
      • 设置依赖必须再把操作添加到队列之前
    • 操作的监听
      • -completionBlock;
      • -setCompletionBlock:block;
      • 当要实现在A操作完成后实现B操作时需要监听A操作
  • NSOperation实现线程间的通信(下载两张图片并合成)

    • 思路
      • 封装下载图片1操作download1
      • 封装下载图片2操作download2
      • 封装合成图片操作combie
      • 创建队列
      • 设置操作依赖
      • 把操作添加到队列中
    • 代码实现
    /** 下载两张图片并合成 */-combieDownloadPicture{__block UIImage *image1;__block UIImage *image2;// 封装下载图片1操作NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{ NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://pic14.nipic.com/20110522/7411759_164157418126_2.jpg"]]; image1 = [UIImage imageWithData:data];}];// 封装下载图片2操作NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{ NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://pic28.nipic.com/20130402/9252150_190139450381_2.jpg"]]; image2 = [UIImage imageWithData:data];}];// 封装合成图片操作NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{ // 获取图形上下文 UIGraphicsBeginImageContext(CGSizeMake); // 画图1 [image1 drawInRect:CGRectMake(0, 0, 240, 120)]; // 画图2 [image2 drawInRect:CGRectMake(0, 120, 240, 120)]; // 生成图片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 关闭图形上下文 UIGraphicsEndImageContext(); // 回到主线程刷新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }];}];// 创建队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 设置依赖[combie addDependency:download1];[combie addDependency:download2];// 把操作添加到队列中[queue addOperation:download1];[queue addOperation:download2];[queue addOperation:combie];
    

七、操作依赖

  • 1、目的 -> NSOperation之间可以设置依赖来保证执行顺序

    • 例如:一定要让操作A执行完后,才能执行操作B,可以这么写
  • 只要添加了依赖, 那么就会等依赖的任务执行完毕, 才会执行当前任务

[operationB addDependency:operationA];// 操作B依赖于操作A , A操作执行完才会执行操作B

+ 2、除了同一quere操作间建立依赖关系,当然也可以在不同queue的NSOperation之间创建依赖关系

+ 注意点:
- 不能相互依赖 -> 比如A依赖B,B依赖A

> 经典实例:合成图片

```objc
#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];

 __block UIImage *image1 = nil;
 __block UIImage *image2 = nil;
 // 1.开启一个线程下载第一张图片
 NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
     NSURL *url = [NSURL URLWithString:@"http://cdn.cocimg.com/assetslogo.png?v=201510272"];
     NSData *data = [NSData dataWithContentsOfURL:url];
     // 2.生成下载好的图片
     UIImage *image = [UIImage imageWithData:data];
     image1 = image;
 }];

 // 2.开启一个线程下载第二长图片
 NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
     NSURL *url = [NSURL URLWithString:@"https://www.baidu.com/img/bd_logo1.png"];
     NSData *data = [NSData dataWithContentsOfURL:url];
     // 2.生成下载好的图片
     UIImage *image = [UIImage imageWithData:data];
     image2 = image;

 }];
 // 3.开启一个线程合成图片
 NSOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
     UIGraphicsBeginImageContext(CGSizeMake(200, 200));
     [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
     [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
     UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();

     // 4.回到主线程更新UI
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
         NSLog(@"回到主线程更新UI");
         self.imageView.image = newImage;
     }];
 }];


 // 监听任务是否执行完毕
 op1.completionBlock = ^{
     NSLog(@"第一张图片下载完毕");
 };
 op2.completionBlock = ^{
     NSLog(@"第二张图片下载完毕");
 };

 // 添加依赖
 // 只要添加了依赖, 那么就会等依赖的任务执行完毕, 才会执行当前任务
 // 注意:
 // 1.添加依赖, 不能添加循环依赖
 // 2.NSOperation可以跨队列添加依赖
 [op3 addDependency:op1];
 [op3 addDependency:op2];

 // 将任务添加到队列中
 [queue addOperation:op1];
 [queue addOperation:op2];
 [queue2 addOperation:op3];
}
@end

将取出的NSOperation封装的操作放到一条新线程中执行

然后将NSOperation对象添加到NSOperationQueue中

}

NSOperation的子类

系统会自动将NSOperationQueue中的NSOperation取出来

```

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

将取出的NSOperation封装的操作放到一条新线程中执行

使用NSOperation子类的方式有3种

二.NSOperation的子类

NSInvocationOperation

1.NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类.

NSBlockOperation

2.使用NSOperation子类的方式有3种

自定义子类继承NSOperation,实现内部相应的方法

NSInvocationOperation

本文由胜博发-编程发布,转载请注明来源:实际上NSOperation是基于GCD更高一层的封装,必须使