NSOperation和NSOperationQueue

基本概念

  • NSOperationNSOperationQueue实现多线程的步骤
    • 首先,将需要执行的任务封装到一个NSOperation对象中
    • 然后,将NSOperation对象添加到NSOperationQueue中
    • 系统会自动将NSOperationQueue中的NSOperation取出来放到一条新线程中执行
  • NSOperation是个抽象类,并不具备封装任务的能力,必须通过它的子类来封装任务
    • NSInvocationOperation
    • NSBlockOperation
    • 自定义继承自NSOperation的操作,实现内部相应的方法

NSOperation

NSInvocationOperation的使用

  • 创建NSInvocationOperation对象
      // 对象方法
      - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
    
  • 必须调用start方法才开始执行操作
      // 一旦执行操作,就会调用target的sel方法
      - (void)start;
    
  • 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
  • 只有将NSOperation放到一个NSOperationQueue中,才会自动异步执行操作
  • 示例

      - (void)invocationOperation
      {
          NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
          // 不会开新线程执行任务
          [op start];
      }
    
      - (void)run
      {
          NSLog(@"------%@", [NSThread currentThread]);
      }
    

NSBlockOperation

  • 创建NSBlockOperation对象
      // 类方法
      + (id)blockOperationWithBlock:(void (^)(void))block;
    
  • 通过addExecutionBlock:方法添加更多的操作
      - (void)addExecutionBlock:(void (^)(void))block;
    
  • 只有当NSBlockOperation封装的任务数 > 1,才会异步执行操作
  • 示例

      - (void)blockOperation
      {
          // 只要NSBlockOperation封装的操作数 > 1,就会【异步】执行操作
          NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
              // 这个任务是在【主线程】
              NSLog(@"下载1------%@", [NSThread currentThread]);
          }];
    
          // 添加额外的任务(在【子线程】执行)
          [op addExecutionBlock:^{
              NSLog(@"下载2------%@", [NSThread currentThread]);
          }];
          [op addExecutionBlock:^{
              NSLog(@"下载3------%@", [NSThread currentThread]);
          }];
          [op addExecutionBlock:^{
              NSLog(@"下载4------%@", [NSThread currentThread]);
          }];
    
          [op start];
      }
    

自定义继承自NSOperation的操作

  • 重写-(void)main方法,在里面实现想执行的任务逻辑
  • 重写-(void)main方法的注意点
    • 创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
    • 在合适的地方通过-(BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
  • 示例

      /**
       * 在这个方法里定义需要执行的任务逻辑
       */
      -(void)main
      {
          //【创建自动释放池】,在大括号结束后这段代码产生的消耗就都被释放了
          @autoreleasepool
          {
              for (NSInteger i = 0; i < 1000; i++)
              {
                  NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
              }
    
              // 由于任务一旦开始执行就没办法停止下来
              // 苹果官方建议,如果是自定义的Operation,而且内部的执行逻辑很耗时
              // 如果外面调用了cancel方法,可以通过在一段耗时逻辑后尝试调用一次isCancelled方法来判断操作是否已经取消,以用来中断任务的执行
              // 注意必须合理布局此isCancelled方法
              if (self.isCancelled) return;
    
              for (NSInteger i = 0; i< 1000; i++)
              {
                  NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
              }
    
              if (self.isCancelled) return;
    
              for (NSInteger i = 0; i < 1000; i++)
              {
                  NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
              }
    
              if (self.isCancelled) return;
          }
      }
    

NSOperation设置依赖关系

  • 比如一定要让操作A执行完后,才能执行操作B,可以这么写
      // 操作B依赖于操作A
      [operationB addDependency:operationA];
    
  • 可以不同队列的NSOperation之间设置依赖关系
  • NSOperation之间不能相互依赖,比如:A依赖B,B依赖A
  • 示例

      - (void)addDependencyTest
      {
          // 直接创建
          NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
          NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"download1----%@", [NSThread  currentThread]);
          }];
          NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"download2----%@", [NSThread  currentThread]);
          }];
          NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"download3----%@", [NSThread  currentThread]);
          }];
    
          // 设置依赖后再添加到队列
          // 当op1和op2都执行完才执行op3,但是op1和op2谁先执行完不确定
          [op3 addDependency:op1];
          [op3 addDependency:op2];
    
          // 【不能】相互依赖
          //    [op3 addDependency:op1];
          //    [op1 addDependency:op3];
    
          [queue addOperation:op1];
          [queue addOperation:op2];
          [queue addOperation:op3];
      }
    

NSOperation执行完毕后想做一些事情,可以通过下面的block设置

  • completionBlock
    -(void (^)(void))completionBlock;
    -(void)setCompletionBlock:(void (^)(void))block;
    
  • 示例

      NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"download----%@", [NSThread  currentThread]);
          }];
    
      // 任务执行完毕就会执行这个block
      op.completionBlock = ^{
          // 也是在子线程中执行
          NSLog(@"op执行完毕后执行---%@", [NSThread currentThread]);
      };
    

NSOperationQueue

  • NSOperationQueue的作用
    • 上面已经讨论过NSOperation是通过调用start方法来执行任务,但默认同步执行的
    • 如果将NSOperation添加到NSOperationQueue中,系统会自动异步执行NSOperation中的操作,实质上NSOperationQueue内部也是自动调用了NSOperation的start方法来执行任务
    • 这种队列同时包含了串行并发功能
  • 添加操作到NSOperationQueue中方法

      -(void)addOperation:(NSOperation *)op;
    
      // 个人更喜欢这种方式添加任务
      -(void)addOperationWithBlock:(void (^)(void))block;
    
  • 最大并发数:maxConcurrentOperationCount

    • 定义:同时执行的任务数
    • 当最大并发数为1的时候就是串行队列,但所有任务不一定在同一个线程上执行完,可能在多个线程上执行,但是一定是按顺序执行
    • 最大并发数设置

      -(NSInteger)maxConcurrentOperationCount;
      -(void)setMaxConcurrentOperationCount:(NSInteger)cnt;
      
      // 调用示例
      self.queue.maxConcurrentOperationCount = 1;
      
  • 暂停和恢复队列:suspended

    • 暂停后还未执行的任务将不会被执行,但是已经开始的任务是不能停下来了

      // YES代表暂停(挂起)队列,NO代表恢复队列
      -(void)setSuspended:(BOOL)b;
      -(BOOL)isSuspended;
      
      // 调用示例
      self.queue.suspended = !self.queue.suspended;
      
  • suspended的应用场景:提高用户体验

    • 场景:队列正在执行一些耗时操作,当用户执行滚动操作时,为了用户体验可以调用这个属性先让队列中的任务暂停执行,以用来提高用户体验
    • 解惑:虽然队列中的任务在子线程中执行,但是由于队列中的任务很耗,所以会影响资源的抢夺,导致一些不好的用户体验
  • 取消队列的所有操作:cancelAllOperations

    • 取消队列中的所有任务,一旦取消就会将队列中任务全部移除
    • 这个方法内部其实是调用了NSOperation中的cancel方法
    • 所以可以通过NSOperation中的cancel方法来取消单个操作
    • 只能取消未执行的任务,已经开始的任务不能取消,除非任务中主动判断了是否取消操作queue.isCancelled来中断操作

      -(void)cancelAllOperations;
      
      // 调用示例
      [queue cancelAllOperations];
      

GCD的队列类型和NSOperationQueue队列比较

GCD的队列类型

  • 并发队列
    • 自己创建的(类型:DISPATCH_QUEUE_CONCURRENT)
    • 全局队列
  • 串行队列
    • 自己创建的(类型:DISPATCH_QUEUE_SERIAL or NULL)
    • 主队列(特殊的串行队列)

NSOperationQueue队列类型

  • 主队列
    • [NSOperationQueue mainQueue]
    • 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行
  • 非主队列(其他队列)
    • 创建:[[NSOperationQueue alloc] init]
    • 同时包含了【串行】、【并发】功能
    • 当设置最大并发数为queue.maxConcurrentOperationCount = 1时就是串行队列
    • 添加到这种队列中的任务(NSOperation),会自动放到子线程中执行

线程间通信

-(void)test
{
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^
    {
        // 图片的网络路径
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];

        // 加载图片
        NSData *data = [NSData dataWithContentsOfURL:url];

        // 生成图片
        UIImage *image = [UIImage imageWithData:data];

        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^
        {
            self.imageView.image = image;
        }];
    }];
}

results matching ""

    No results matching ""