美文网首页
NSOperation

NSOperation

作者: Carson_Zhu | 来源:发表于2018-02-11 18:52 被阅读15次

    简介

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

    GCD和NSOperation区别

    • 依赖关系
      NSOperation可以设置操作之间的依赖(可以跨队列设置),GCD无法设置依赖关系,不过可以通过同步来实现这种效果。
    • KVO(键值对观察)
      NSOperation容易判断操作当前的状态(是否执行、是否取消等),对此GCD无法通过KVO进行判断。
    • 优先级
      NSOperation可以设置自身的优先级,但是优先级高的不一定先执行,GCD只能设置队列的优先级,如果要区分block任务的优先级,需要很复杂的代码才能实现。
    • 继承
      NSOperation是一个抽象类,实际开发中常用的是它的两个子类:NSInvocationOperationNSBlockOperation,同样我们可以自定义NSOperationGCD执行任务可以自由组装,没有继承那么高的代码复用度。
    • 效率
      直接使用GCD效率确实会更高效,NSOperation会多一点开销,但是通过NSOperation可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己。
    • 可操作性
      NSOperation可以随时取消准备执行的任务(已经在执行的不能取消),GCD没法停止已经加入queueblock(虽然也能实现,但是需要很复杂的代码)。

    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)}
      
      结论:
      可以看出NSOperationQueueaddOperationWithBlock:能够开启新线程,进行并发执行。
    串行执行和并发执行

    之前我们说过,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还要简单了许多?

    操作依赖

    NSOperationNSOperationQueue最吸引人的地方是它能添加操作之间的依赖关系。比如说有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;
      

    相关文章

      网友评论

          本文标题:NSOperation

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