美文网首页
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