美文网首页ios开发那些事工作生活 Objective - C 开发那些事
深入浅出iOS多线程(四)——NSOperation多线程

深入浅出iOS多线程(四)——NSOperation多线程

作者: struggle3g | 来源:发表于2019-07-03 01:10 被阅读47次

    深入浅出iOS多线程(一)——线程的概念
    深入浅出iOS多线程(二)——pthraed和NSThread的使用
    深入浅出iOS多线程(三)——GCD多线程
    深入浅出iOS多线程(四)——NSOperation多线程
    深入浅出iOS多线程(五)——多线程锁

    NSOperation的作用

    简介

    和GCD一样,NSOperation也是并发编程技术,NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,比GCD更加简单,更加方便

    NSoperation需要配合NSOperationQueue来实现多线程,NSOperation单独使用时系统同步执行操作,并没有开辟新的线程的能力,只有配合NSoperationQueue才能实现异步执行。

    NSOperation是苹果大力推荐的"并发"技术
    NSOperation的核心概念是,将"操作"添加进"队列"
    GCD将"任务"添加到"队列"

    • NSOperation是一个抽象类
      • 特点:不能直接使用
      • 目的:定义子类共有的属性和方法
      • 子类:NSInvocationOperation、NSBlockOperation

    由于NSOperation是基于GCD的,使用步骤也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列,NSOperation实现多线程的使用步骤分3步:

    • 创建操作:创建NSOperation子类对象
    • 创建队列:创建NSOperationQueue队列
    • 将操作添加到队列中去:NSOperation添加到NSOperationQueue中去

    系统会自动取出"队列"中的"操作"执行。

    NSOperation多线程的基本使用

    NSInvocationOperation的基本使用

    • 将NSOperation添加到队列,会自动异步执行调度方法

      NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"];
      NSOperationQueue * q = [[NSOperationQueue alloc]init];
      [q addOperation:op];
      
    • 打印结果

      <NSThread: 0x600001f536c0>{number = 3, name = (null)}  invocation
      
    • 将NSOperation添加到队列,会自动异步执行调度方法

    根据上述代码我们可以确信NSOperationQueue是一个队列,而NSInvocationOperation是一个操作(任务),那么这个队列到底是什么样的队列,而这个操作是什么样的操作,在上述代码中队列添加更多的"操作",代码如下:

    //队列
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
        
    for (int i = 0; i<10; i++) {
        NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@(i)];
        //将操作添加到队列 - 会自动异步执行调度方法
        [q addOperation:op];
    }
    

    打印结果:

    <NSThread: 0x6000016c8cc0>{number = 4, name = (null)}  1
    <NSThread: 0x6000016f4140>{number = 3, name = (null)}  0
    <NSThread: 0x6000016c0a00>{number = 6, name = (null)}  3
    <NSThread: 0x6000016cc380>{number = 5, name = (null)}  2
    <NSThread: 0x6000016f4280>{number = 7, name = (null)}  4
    <NSThread: 0x6000016f4140>{number = 3, name = (null)}  8
    <NSThread: 0x6000016cc380>{number = 5, name = (null)}  6
    <NSThread: 0x6000016c0a00>{number = 6, name = (null)}  7
    <NSThread: 0x6000016c8cc0>{number = 4, name = (null)}  5
    <NSThread: 0x6000016f4280>{number = 7, name = (null)}  9
    
    

    从打印结果上面看,线程不一样,执行顺序也不一样,说明这个队列是并发队列,队列中的操作是异步操作。

    NSBlockOperation的基本使用

    NSBlockOperationNSInvocationOperation使用步骤几乎一摸一样,不一样的地方在于,NSBlockOperationNSInvocationOperation省略了一个@selector,代码更加简洁。

    
    /**
        第一种block形式
    **/
    //1.队列
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
        
    //2.操作
    for (int i = 0; i<10; i++) {
        NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
             [NSThread currentThread].name = [@(i) stringValue];
            NSLog(@"%@",[NSThread currentThread]);
        }];
        //将操作添加到队列 - 会自动异步执行调度方法
        [q addOperation:op];
    }
    
    /**
        第一种block形式
    **/
    //1.队列
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
        
    //2.操作
    for (int i = 0; i<10; i++) {
        //将操作添加到队列 - 会自动异步执行调度方法
        [q addOperationWithBlock:^{
             [NSThread currentThread].name = [@(i) stringValue];
            NSLog(@"%@",[NSThread currentThread]);
        }];
    }
    

    打印结果:

    <NSThread: 0x6000001e1900>{number = 5, name = 1}
    <NSThread: 0x6000001e1940>{number = 6, name = 3}
    <NSThread: 0x6000001ea900>{number = 3, name = 0}
    <NSThread: 0x6000001e16c0>{number = 4, name = 2}
    <NSThread: 0x6000001ea900>{number = 3, name = 5}
    <NSThread: 0x6000001e1940>{number = 6, name = 4}
    <NSThread: 0x6000001e1900>{number = 5, name = 6}
    <NSThread: 0x6000001e16c0>{number = 4, name = 7}
    <NSThread: 0x6000001eaf40>{number = 7, name = 8}
    <NSThread: 0x6000001ea900>{number = 3, name = 9}
    

    上述代码运行结果,同样可以知道这个队列是并发队列,队列中的操作是异步操作。

    上述代码,所有的代码都要添加一个队列怎么办?

    • self中定义一个类属性,在配合懒加载的形式,这样的话不管这个类中哪个方法有添加队列的操作,那么都可以使用这一个"队列",这样会让代码更加简洁。
    @interface ViewController ()
    @property (nonatomic,strong)NSOperationQueue * MyQueue;
    @end
    
    - (NSOperationQueue *)MyQueue{
        
        if(!_MyQueue){
            _MyQueue = [[NSOperationQueue alloc]init];
        }
        return _MyQueue;
    }
    
    for (int i = 0; i<10; i++) {
        //将操作添加到队列 - 会自动异步执行调度方法
        [self.MyQueue addOperationWithBlock:^{
            [NSThread currentThread].name = [@(i) stringValue];
            NSLog(@"%@",[NSThread currentThread]);
        }];
    }
    

    很简洁很爽

    NSOperation线程间的通信

    //小清新
    [self.MyQueue addOperationWithBlock:^{
        NSLog(@"耗时操作");
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            NSLog(@"更新UI");
        }];
    }];
    

    NSOperation最大并发数

    • 从 iOS 8.0 开始,无论使用 GCD还是 NSOperation ,都会开启很多线程
    • 在 iOS 7.0 以前,GCD 通常只会开启 5 6条线程!
    • 目前线程多了说明:
      1. 底层的现场池更大了,能够拿到的线程资源多了!
      2. 多控制同时并发的现场数,要求就更高了!
    //添加操作进队列
    /*
     从 iOS 8.0 开始,无论使用 GCD还是 NSOperation ,都会开启很多线程
     在 iOS 7.0 以前,GCD 通常只会开启 5  6条线程!
     目前线程多了说明:
     1.底层的现场池更大了,能够拿到的线程资源多了!
     2.多控制同时并发的现场数,要求就更高了!
     */
        
    for (int i = 0;i < 20; i++) {
        [self.MyQueue addOperationWithBlock:^{
            NSLog(@"%@---%d",[NSThread currentThread],i);
        }];
    }
    

    打印结果:

    <NSThread: 0x600003e984c0>{number = 4, name = (null)}---1
    <NSThread: 0x600003e98540>{number = 5, name = (null)}---2
    <NSThread: 0x600003e96980>{number = 6, name = (null)}---3
    <NSThread: 0x600003e9c6c0>{number = 3, name = (null)}---0
    <NSThread: 0x600003e984c0>{number = 4, name = (null)}---4
    <NSThread: 0x600003e96980>{number = 6, name = (null)}---5
    <NSThread: 0x600003e98540>{number = 5, name = (null)}---6
    <NSThread: 0x600003e9c6c0>{number = 3, name = (null)}---7
    <NSThread: 0x600003e984c0>{number = 4, name = (null)}---8
    <NSThread: 0x600003e96980>{number = 6, name = (null)}---9
    <NSThread: 0x600003e98540>{number = 5, name = (null)}---10
    <NSThread: 0x600003e9e280>{number = 7, name = (null)}---11
    <NSThread: 0x600003e8f080>{number = 8, name = (null)}---12
    <NSThread: 0x600003e8f280>{number = 9, name = (null)}---13
    <NSThread: 0x600003e9c6c0>{number = 3, name = (null)}---14
    <NSThread: 0x600003e984c0>{number = 4, name = (null)}---15
    <NSThread: 0x600003e98540>{number = 5, name = (null)}---16
    <NSThread: 0x600003e9e700>{number = 10, name = (null)}---17
    <NSThread: 0x600003e9e740>{number = 11, name = (null)}---18
    <NSThread: 0x600003e96980>{number = 6, name = (null)}---19
    
    • 通常设置同时最大的并发操作数量

      • WIFI: 5 至 6
      • 流量 : 2 到 3
      self.MyQueue.maxConcurrentOperationCount = 2;
      
      for (int i = 0;i < 20; i++) {
          [self.MyQueue addOperationWithBlock:^{
              [NSThread sleepForTimeInterval:1.0];
              NSLog(@"%@---%d",[NSThread currentThread],i);
          }];
      }
      

    打印结果:

    <NSThread: 0x6000002d4f40>{number = 4, name = (null)}---0
    <NSThread: 0x6000002ad700>{number = 3, name = (null)}---1
    <NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---2
    <NSThread: 0x6000002d5000>{number = 5, name = (null)}---3
    <NSThread: 0x6000002d5000>{number = 5, name = (null)}---5
    <NSThread: 0x6000002ad700>{number = 3, name = (null)}---4
    <NSThread: 0x6000002ad700>{number = 3, name = (null)}---7
    <NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---6
    <NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---9
    <NSThread: 0x6000002d4f40>{number = 4, name = (null)}---8
    <NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---10
    <NSThread: 0x6000002d4f40>{number = 4, name = (null)}---11
    <NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---13
    <NSThread: 0x6000002ad700>{number = 3, name = (null)}---12
    <NSThread: 0x6000002ad700>{number = 3, name = (null)}---15
    <NSThread: 0x6000002d4f40>{number = 4, name = (null)}---14
    <NSThread: 0x6000002d5000>{number = 5, name = (null)}---16
    <NSThread: 0x6000002ad700>{number = 3, name = (null)}---17
    <NSThread: 0x6000002ad700>{number = 3, name = (null)}---19
    <NSThread: 0x6000002d4f40>{number = 4, name = (null)}---18
    

    为什么实际当中打印会多出几个线程?

    1. 线程中的任务完成以后会回收线程
    2. 当一个任务完成需要从队列中取新的任务,取一个空间的线程,或者是开辟新线程的时候,而1中的线程正在回收,所以这个任务开辟的线程会比原先的线程number+1

    NSOperationQueue的属性和方法

    suspended

    • NSOperationQueue的一个属性,可以控制NSOperationQueue队列的挂起还是继续

      • isSuspended 判断是否是挂起
      • suspended 修改NSOperationQueue挂起或者继续
      //队列是否挂起
      if(self.MyQueue.isSuspended){
          
          NSLog(@"继续");
          self.MyQueue.suspended = NO;
          
      }else{
          NSLog(@"暂停");
          self.MyQueue.suspended = YES;
      }
      
      -(void)demo1{
          self.MyQueue.maxConcurrentOperationCount = 2;
              
          for (int i = 0;i < 20; i++) {
              [self.MyQueue addOperationWithBlock:^{
                  [NSThread sleepForTimeInterval:1.0];
                  NSLog(@"%@---%d",[NSThread currentThread],i);
              }];
          }
      }
      

    operationCount

    可以拿到队列中的操作数

    NSLog(@"%tu",self.MyQueue.operationCount);
    

    取消NSOperationQueue中的所有操作

    注意:

    1. 队列挂起的时候,取消队列所有操作,不会清空队列的operationCoount只有在队列继续的时候才能清空
    2. 正在执行的操作也不会被清空,也不会被取消
    [self.MyQueue cancelAllOperations];
    
    

    NSOperation依赖关系(Dependency)

    waitUntilFinished中的YES会卡住当前线程

    [self.MyQueue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
    

    NSOperation依赖关系:

    - (void)addDependency:(NSOperation *)op;
    - (void)removeDependency:(NSOperation *)op;
    

    例子:

    /*
     *  例子:下载/解压/通知用户
     **/
        
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载----%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"解压----%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"通知用户----%@",[NSThread currentThread]);
    }];
        
    //NSOperation 提供了依赖关系
    //注意:不要指定循环依赖,队列就不工作了,不会造成死锁。
    [op2 addDependency:op1];
    [op3 addDependency:op2];
        
    //YES 会卡住当前线程
    [self.MyQueue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
    NSLog(@"come here  %@",[NSThread currentThread]);
        
        //主线程通知用户
    [[NSOperationQueue mainQueue]addOperation:op3];
        
    

    打印结果:

    下载----<NSThread: 0x600002dc3240>{number = 3, name = (null)}
    解压----<NSThread: 0x600002dc5440>{number = 4, name = (null)}
    通知用户----<NSThread: 0x600002dc3240>{number = 3, name = (null)}
    come here  <NSThread: 0x600002d9cb00>{number = 1, name = main}
    

    总结

    • 只要是NSOperation的子类,就可以添加到NSOperationQueue
    • 如果使用NSOperation可以使代码更加简洁,代码会更加工整,NSOperation能办到的事情,GCD也都可以办到。
    • NSOperation相比GCD节约来代码行数,并且是面向对象的。
    • NSOperation是苹果大力推荐的并发技术。
    • NSOperation是一个抽象类
      • 特点:不能直接使用
      • 目的:定义子类共有的属性和方法
      • 子类:NSInvocationOperation、NSBlockOperation

    GCD 和 NSOperation 对比

    GCD 在 iOS 4.0 推出,主要针对多核处理器做了优化的并发技术,是C语言的

    • 将"任务"[block]添加到 队列[串行/并发/主队列/全局队列] ,并且指定执行任务的函数[同步/异步]
    • 线程间的通讯 dispatch_get_main_queue()
    • 提供了一些 NSOperation 不具备的功能
      • 一次执行
      • 延迟执行
      • 调度组(在op中也可以做到,有点麻烦)
      • 信号量
      • apply(重复)

    NSOperation 在 iOS 2.0 推出的,苹果推出 GCD以后,对NSOperation 底层做了重写!

    • 将操作[异步执行的任务] 添加到队列[并发队列],就会立刻异步执行
    • mainQueue
    • 提供了一些GCD 实现起来比较困难的功能
    • 最大并发线程
    • 队列的暂停/继续
    • 取消所有操作
    • 指定操作之间的依赖关系(GCD 用同步来实现)

    多线程NSOperation结构图

    NSOperation.png

    相关文章

      网友评论

        本文标题:深入浅出iOS多线程(四)——NSOperation多线程

        本文链接:https://www.haomeiwen.com/subject/whffhctx.html