iOS NSOperation 和 NSOperationQueue

641 阅读16分钟

一. 概念

NSOperationNSOperationQueue 是苹果对GCD面向对象的封装,它的底层是基于GCD实现的,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。

二、对比GCD

  • 提供可选实现的任务完成block。
  • 支持任务之间添加依赖关系, 控制执行顺序。
  • 支持设置任务的优先级, 可控制任务的相对执行顺序。
  • 可使用KVO监听任务执行状态变化:isExecuteing、isFinished、isCancelled。
  • 可以很方便的取消一个操作的执行。
  • 设置最大并发数

三、任务 NSOperation

NSOperation 就是对任务进行的封装,封装好的任务交给不同的 NSOperationQueue 即可进行串行队列的执行或并发队列的执行。

NSOperation 是一个抽象类,并不能直接使用,必须使用它的子类,有三种方式:NSInvocationOperationNSBlockOperation自定义子类继承NSOperation,前两种是苹果为我们封装好的,可以直接使用,自定义子类,需要我们实现相应的方法。

1、使用子类 NSInvocationOperation

/*
创建一个NSInvocationOperation对象,指定执行的对象和方法
该方法可以接收一个参数即object
*/
NSInvocationOperation *invocationOpeartion = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOpeartion) object:nil];
    
// 如果没有添加到 NSOperationQueue, 则需要手动调用 start
[invocationOpeartion start];

/// 回调方法
- (void)invocationOpeartion {
    for (int i = 0; i < 5; i++) {
        NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
    }
}

// 打印: Task1 <NSThread: 0x6000019581c0>{number = 1, name = main} 0

注意: NSInvocationOperation 单独使用时, 并没有开启新的线程, 任务都是在当前线程中执行。只有添加到队列中才会开启新的线程。即默认情况下,如果操作没有放到队列中queue中,都是同步执行。只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。

2、使用子类 NSBlockOperation

  • 2.1 实例1:
//创建一个NSBlockOperation对象,传入一个block
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 5; I++)
    {
        NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
    }
}];
// 执行
[operation start];
// 打印: Task1 <NSThread: 0x6000019581c0>{number = 1, name = main} 0

NSBlockOperation 封装的操作数 == 1时,并没有开启新的线程, 任务都是在当前线程中执行。

  • 2.2 实例2:
 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; I++)
        {
            NSLog(@"task1=====%@ %d", [NSThread currentThread], i);
        }
    }];
    
    [operation addExecutionBlock:^{
       
        NSLog(@"task2=====%@",[NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
       
        NSLog(@"task3=====%@",[NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
       
        NSLog(@"task4=====%@",[NSThread currentThread]);
    }];
    
    [operation start];
/*
task3=====<NSThread: 0x600000509840>{number = 6, name = (null)}
task4=====<NSThread: 0x600000530200>{number = 3, name = (null)}
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 0
task2=====<NSThread: 0x600000511680>{number = 5, name = (null)}
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 1
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 2
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 3
task1=====<NSThread: 0x600000558880>{number = 1, name = main} 4
*/

NSBlockOperation 类有一个实例方法 addExecutionBlock,这个方法可以添加一个代码块,当执行 NSBlockOperation 对象 start 时,该对象将其所有块提交给默认优先级的并发调度队列。然后对象等待所有的块完成执行。当最后一个块完成执行时,操作对象将自己标记为已完成。由于块操作本身在单独的线程上运行, 所以应用程序的其他线程可以在等待块操作完成的同时继续工作
需要说明的一点是,如果添加的任务较多的话,这些操作(包括 blockOperationWithBlock 中的操作)可能在不同的线程中并发执行,这是由系统决定的。

使用 addExecutionBlock 追加的任务是并发执行的,如果NSBlockOperation 封装的操作数 > 1,就会开启子线程,异步执行任务,这里追加的任务不一定就是子线程,也有可能是主线程。

  • 2.3 设置完成回调
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    // 任务代码
    NSLog(@"1");
}];

// 设置任务1完成的回调block
[operation1 setCompletionBlock:^{
        NSLog(@"任务1完成了");
}];

3、使用自定义继承自NSOperation的子类

image.png 参考Apple官方文档:developer.apple.com/documentati…

3.1、先定义一个继承自NSOperation的子类,对于非并发操作,通常仅重写一个方法:重写main方法。

在此方法中,您可以放置执行给定任务所需的代码。当然,您还应该自定义初始化方法,以便更轻松地创建自定义类的实例。您可能还想定义 getter 和 setter 方法来访问操作中的数据。但是,如果您确实自定义 getter 和 setter 方法,则必须确保可以从多个线程安全地调用这些方法(加锁)。

@interface Operation : NSOperation

@end

@implementation Operation

-(void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 4; i++) {
            sleep(2);
            NSLog(@"%d==%@", i, NSThread.currentThread);
        }
    }
}

@end

// 使用:
Operation *op = [[Operation alloc] init];
[op start];

注意: 自定义的Operation并没有开启新的线程,任务的执行是在当前的线程中执行的。

3.2、先定义一个继承自NSOperation的子类,如果要创建并发操作,则至少需要重写以下方法和属性:
  • start()

  • isAsynchronous

  • isExecuting

  • isFinished

  • 在并发操作中,您的start()方法负责以异步方式启动操作。无论您是生成线程还是调用异步函数,您都可以从此方法中执行。启动操作后,您的start()方法还应根据属性报告更新操作的执行状态。您可以通过发送关键路径的 KVO 通知来执行此操作,这会让感兴趣的客户端知道操作正在运行。-- isExecuting

  • 完成或取消任务后,并发操作对象必须为和关键路径生成 KVO 通知,以标记操作状态的最终变化。(在取消的情况下,即使操作未完全完成其任务,更新关键路径仍然很重要。排队操作必须报告它们已完成,然后才能从队列中删除。)除了生成 KVO 通知之外,您对和属性的覆盖还应继续根据操作状态报告准确的值。-- isFinished

  • 如果您自定义操作的依赖项功能,则可能必须重写其他方法并提供其他 KVO 通知。

@interface JDTestOperation()
@property(assign,nonatomic,getter= isExecuting)BOOLexecuting;
@property(assign,nonatomic,getter= isFinished)BOOLfinished;
@end
@implementation JDTestOperation
@synthesizeexecuting =_executing;
@synthesizefinished =_finished;
- (void)main
{
        [self setExecuting:YES];
        NSLog(@"%@ start",self.opName);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
                //do something really important
                sleep(1);
                NSLog(@"%@ end",self.opName);
                [self setExecuting:NO];
                [self setFinished:YES];
        });
}
- (void)setFinished:(BOOL)finished
{
        [self willChangeValueForKey:@"isFinished"];
        _finished= finished;
        [self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing
{
        [self willChangeValueForKey:@"isExecuting"];
        _executing= executing;
        [self didChangeValueForKey:@"isExecuting"];
}

四、任务队列 NSOperationQueue

如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。

  • 这里的队列是指操作队列,即用来存放操作的队列。不同于GCD的先进先出,NSOperationQueue 可以设置操作执行的优先级
  • 操作队列通过设置最大并发操作数(maxConcurrentOperationCount) 来控制并发、串行。
  • NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列,主队列运行在主线程之上,而自定义队列在后台执行。

1、NSOperationQueue 设置操作执行的优先级

NSOperation 优先级
NSOperation 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。

默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal。但是我们可以通过setQueuePriority: 方法来改变当前操作在同一队列中的执行优先级。

// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

将操作添加到队列后操作会进入就绪状态,就绪状态的操作的执行顺序由操作之间的优先级决定

就绪状态的操作:简单理解就是准备就绪的操作就是没有需要依赖的操作,例如op2依赖于op1,那么op2就没有准备就绪,因为他需要等op1执行完才准备就绪

优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系。

2、NSOperationQueue 设置最大并发操作数

使用NSOperation Queue创建的自定义队列同时具有串行、并发执行的能力,这里的关键就是maxConcurrentOperationCount这个关键属性,叫做最大并发操作数,用来控制一个特定队列中可以有多少个操作同时参与并发执行。

  • maxConcurrentOperationCount = 0,不执行。

  • maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行,在子线程中执行任务。

  • maxConcurrentOperationCount 为1时,队列为串行队列,按顺序串行执行,并且一个操作完成之后,下一个操作才开始执行。

  • maxConcurrentOperationCount 大于1时,队列为并发队列,在子线程中操作并发执行,而开启线程数量是由系统决定的。

  • maxConcurrentOperationCount值并不是表示并发执行的线程数量,而是在一个队列中能够同时执行的任务的数量。

/**
 * 设置 MaxConcurrentOperationCount(最大并发操作数)
 */
- (void)setMaxConcurrentOperationCount {

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

    // 2.设置最大并发操作数
    queue.maxConcurrentOperationCount = 1; // 串行队列
// queue.maxConcurrentOperationCount = 2; // 并发队列
// queue.maxConcurrentOperationCount = -1; // 不进行限制

    // 3.添加操作
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}

3、NSOperationQueue 提供了主队列和自定义队列

  • 主队列: [NSOperationQueue mainQueue];, 凡是添加到主队列的NSOperation任务都会放到主线程执行。
  • 自定义队列: [[NSOperationQueue alloc] init];, 凡是添加到自定义队列的NSOperation任务都会放到子线程执行。
3.1 添加任务到队列: 将操作加入到操作队列后能够开启新线程,进行并发执行。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 方式1: 
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 任务代码
    for (int i = 0; i < 5; i++) {
        sleep(2);
        NSLog(@"%d -- %@", i, NSThread.currentThread);
    }
}];
[queue addOperation:blockOperation];

// 方式2: 
[queue addOperationWithBlock:^{
    // 任务代码
    for (int i = 0; i < 5; i++) {
        sleep(2);
        NSLog(@"%d -- %@", i, NSThread.currentThread);
    }
}];


注意: 不需要调用 NSBlockOperation 的 start方法

3.2 添加多个操作到队列: 将操作加入到操作队列后能够开启新线程,进行并发执行。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 操作1
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 任务代码
    for (int i = 0; i < 5; i++) {
        sleep(2);
        NSLog(@"%d -- %@", i, NSThread.currentThread);
    }
}];

// 操作2
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];

// 方式1:
[queue addOperation:blockOperation];
[queue addOperation:invocationOperation];

// 方式2:
// waitUntilFinished参数,如果传YES,则表示会等待队列里面的任务执行完成后才会往下执行,也就是会阻塞线程
[queue addOperations:@[blockOperation, invocationOperation] waitUntilFinished:true];

/// 回调方法
- (void)invocationOperation {
    for (int i = 0; i < 5; i++) {
        sleep(2);
        NSLog(@"%d -- %@", i, NSThread.currentThread);
    }
}

提示: waitUntilFinished参数,如果传YES,则表示会等待队列里面的任务执行完成后才会往下执行,也就是会阻塞线程

4、NSOperation 操作依赖

NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。

- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。 - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。 @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

我们现在来假设一个场景,有两个操作A、B,A操作完B才能操作,也就是B依赖A

/**
 * 操作依赖
 * 使用方法:addDependency:
 */
- (void)addDependency {

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

    // 2.创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.添加依赖
    [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2

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

可以看到即使两个操作在不同线程执行也就是并发执行,也是op1先执行才能执行op2

image.png

提示:
queuePriority不能取代依赖关系
queuePriority属性只对同一个队列有效

5、NSOperation、NSOperationQueue 线程间的通信

子线程完成任务, 会主线程更新UI:

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

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 任务代码 -- 异步进行耗时操作
    for (int i = 0; i < 5; i++) {
        NSLog(@"%d -- %@", i, NSThread.currentThread);
    }
    // 回主队列更新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        NSLog(@"更新UI");
    }];
}];
[queue addOperation:blockOperation];

6、NSOperation、NSOperationQueue 线程安全

6.1 NSOperation、NSOperationQueue 非线程安全

下面,我们模拟火车票售卖的方式,实现 NSOperation 线程安全和解决线程同步问题。 场景:总共有100张火车票,有两个售卖火车票的窗口,一个是广东火车票售卖窗口,另一个是广西火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。

/**
 * 非线程安全:不使用 NSLock
 * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
 */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

    self.ticketSurplusCount = 50;

    // 1.创建 queue1,queue1 代表广东火车票售卖窗口
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    queue1.maxConcurrentOperationCount = 1;

    // 2.创建 queue2,queue2 代表广西火车票售卖窗口
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;

    // 3.创建卖票操作 op1
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [self saleTicketNotSafe];
    }];

    // 4.创建卖票操作 op2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [self saleTicketNotSafe];
    }];

    // 5.添加操作,开始卖票
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
}

/**
 * 售卖火车票(非线程安全)
 */
- (void)saleTicketNotSafe {
    while (1) {

        if (self.ticketSurplusCount > 0) {
            //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@"所有火车票均已售完");
            break;
        }
    }
}
6.2 线程安全,加线程锁(使读写操作在同一线程操作,才能保证线程安全。atomic只是保证读写安全)
- (void)saleTicketNotSafe {
    while (1) {
     [self.lock lock];
        if (self.ticketSurplusCount > 0) {
            //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@"所有火车票均已售完");
            break;
        }
      [self.lock unlock];
    }
}

7、NSOperation、NSOperationQueue 常用属性和方法归纳

NSOperation 常用属性和方法

属性:

注意:
1.取消任务和暂停任务一样, 不会取消当前正在执行的任务, 只能取消还未执行的任务
2.如果在任务执行的过程中暂停队列中的任务, 那么当前正在执行的任务并不会被暂停, 而是会暂停队列中的下一个任务
3.恢复任务, 是从队列第一个没有被执行过的任务开始恢复
    /** 是否已经取消线程 */
    @property (readonly, getter=isCancelled) BOOL cancelled;
    /** 是否正在执行 */
    @property (readonly, getter=isExecuting) BOOL executing;
      /** 是否已经完成 */
    @property (readonly, getter=isFinished) BOOL finished;
      /** 是否并行操作 */
    @property (readonly, getter=isConcurrent) BOOL concurrent;
      /** 是否异步操作 */
    @property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
      /** 是否能准备运行,这个值和任务的依赖关系相关 */
    @property (readonly, getter=isReady) BOOL ready;
      /** 得到所有依赖的NSOperation任务 */
    @property (readonly, copy) NSArray<NSOperation *> *dependencies;

      /** 该操作的优先级 */
   @property NSOperationQueuePriority queuePriority;

      /** 操作优先级 */
    /*
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    */
    /** NSOperation执行完毕后调用 */
    @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);

方法:

    /** 开始方法.在当前任务状态和依赖关系合适的情况下,启动NSOperation的main方法任务,需要注意缺省实现只是在当前线程运行。如果需要并发执行,子类必须重写这个方法,并且使 - (BOOL)isConcurrent 方法返回YES */
    - (void)start;
    /** 主方法 start执行后,主类应该重写此方法相比与start方法 */
    - (void)main;
    /** 取消 */
    - (void)cancel;
    /** 添加一个依赖 */ //依赖只是设置先后执行的顺序关系,可以跨队列依赖,不可以循环依赖。添加依赖以后会顺序执行任务,但是不一定开一个线程,可能会开多个线程,但是不会太多
    - (void)addDependency:(NSOperation *)op;
    /** 删除一个依赖 依赖的任务关系不会自动消除,必须调用该方法 */
    - (void)removeDependency:(NSOperation *)op;
    /** 阻塞当前线程,直到该NSOperation结束。可用于线程执行顺序的同步 */
    - (void)waitUntilFinished  //

取消操作方法

- (void)cancel; 可取消操作,实质是标记 isCancelled 状态。

判断操作状态方法

- (BOOL)isFinished; 判断操作是否已经结束。
- (BOOL)isCancelled; 判断操作是否已经标记为取消。
- (BOOL)isExecuting; 判断操作是否正在在运行。
- (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。

操作同步

-(void)waitUntilFinished; 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
- (void)setCompletionBlock:(void (^)(void))block; completionBlock 会在当前操作执行完毕时执行 completionBlock。
- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op;移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

NSOperationQueue 常用属性和方法

属性:

 /** 返回已加入执行operation的数组,当某个operation结束后会自动从这个数组清除 */
 @property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
 /** 当前队列的操作数量 */
 @property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
 /** 最大并发量 (默认是-1,最大为6) */
 @property NSInteger maxConcurrentOperationCount;
 /** 暂停任务和开始任务,挂起的队列不会影响已执行和执行完的任务,队列暂停再添加任务也不会自动执行任务了 */
 @property (getter=isSuspended) BOOL suspended;
 /** 线程队列的名字 */
 @property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
 @property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
 @property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);

方法:

    /** 添加操作 */
    - (void)addOperation:(NSOperation *)op;
    /** 批量参加操作 wait标志是否当前线程等待所有operation结束后,才返回 */
    - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
    /** 添加操作块 */
    - (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
    /** 取消所有操作 取消所有operation的执行,实质是调用各个operation的cancel方法 */
    - (void)cancelAllOperations;
    /** 当前线程等待,直到opA和opB都执行结束 opA opB都是 添加到队列中的NSOperation */
    - (void)waitUntilAllOperationsAreFinished;

#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
    /** 返回当前NSOperationQueue,如果当前线程不是在NSOperationQueue上运行则返回nil */
    @property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);
    /** 返回主线程的NSOperationQueue,缺省总是有一个queue */
    @property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);
#endif

取消/暂停/恢复操作

- (void)cancelAllOperations; 可以取消队列的所有操作。
- (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
- (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。

操作同步:
- (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。

添加/获取操作:
- (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象。
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
- (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
- (NSUInteger)operationCount; 当前队列中的操作数。

**获取队列: **

+ (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
+ (id)mainQueue; 获取主队列。