NSOperation

1.NSOperation的介绍

NSOperation、NSOperationQueue是基于GCD的面向对象的封装。比GCD更简单易用,代码可读性也更高。
NSOperation是基于GCD的封装,所以也有任务(操作)和队列的概念

  • NSOperation(操作):
    • 1.操作即在线程中需要执行的代码。
    • 2.在GCD中是放在block中,在NSOperation中,我们使用NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。NSOperation是抽象类
    • 3创建完的操作如果放入NSOperationQueue,则会异步执行。如果直接调用start方法则不会开辟子线程,会在当前线程执行。
  • NSOperationQueue(操作队列)
    • 1.用来存放操作的队列。
    • 2.队列是串行还是并发根据最大并发操作数(maxConcurrentOperationCount)决定。这个值不是线程的数量,而是一个队列中同时能并发执行的操作数。如果值为1,则是串行队列;如果值大于1,则是并发队列。但是本质上和串行队列还是不同的,因为最大并发数设置为1时,开启的线程数不止一条。
      观察结果可以知道一次只运行一个任务,但是并不是在同一个线程。
      1. NSOperationQueue提供了两种队列,一种是主队列,通过“[NSOperationQueue mainQueue]”获得,添加到主队列的任务在主线程执行。还有一种是自定义队列,会在子线程执行。
    • 4.把操作加入队列的方法
  • (void)addOperation:(NSOperation *)op;
  • (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
  • (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    • 5.操作加入队列后的执行顺序
      最大并发数是否为1,队列内的操作执行顺序依赖下面两个要素:
      如果所插入的操作存在依赖关系,优先完成依赖操作。
      如果所插入的操作不存在依赖关系,最大并发数是1,按先进先出的原则。最大并发数大于1,则开辟新的线程执行。

2NSOperation的基本使用

NSOperation的基本使用步骤:

  • 1.创建操作:将需要操作的对象封装到NSOperation的子类对象中
  • 2.创建队列,设置最大并发数。不设置最大并发数,则由系统调度。
  • 3.把操作加入到队列中(在子线程执行任务)或者直接对操作调用start方法(在当前线程执行)

2.1使用子类NSBlockOperation

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    for (int i = 1; i < 5; i++) {
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            sleep(1);
            NSLog(@"任务%d,当前i线程%@", i, [NSThread currentThread]);
            
        }];
        [queue addOperation:blockOperation];
    }

运行结果:

2020-03-04 14:52:48.986733+0800 Test[68500:4161032] 任务1,当前i线程{number = 4, name = (null)}
2020-03-04 14:52:48.986796+0800 Test[68500:4161031] 任务2,当前i线程{number = 3, name = (null)}
2020-03-04 14:52:49.990794+0800 Test[68500:4161032] 任务3,当前i线程{number = 4, name = (null)}
2020-03-04 14:52:49.990794+0800 Test[68500:4160433] 任务4,当前i线程{number = 5, name = (null)}

因为把操作加入了自定义的队列,所以任务在子线程中执行。
因为设置了最大并发数是2,所以最大可以同时执行两个任务。

2.2使用子类NSInvocationOperation

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    for (int i = 1; i < 5; i++) {
        NSInvocationOperation *invocationOperation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:[NSString stringWithFormat:@"任务%d",i]];
        [queue addOperation:invocationOperation1];
    }

- (void)task:(id)obj{
    sleep(2);
    NSLog(@"执行%@,当前线程%@", obj, [NSThread currentThread]);
}

运行结果:

2020-03-04 15:08:35.180275+0800 Test[68879:4182945] 执行任务2,当前线程{number = 3, name = (null)}
2020-03-04 15:08:35.180285+0800 Test[68879:4182942] 执行任务1,当前线程{number = 4, name = (null)}
2020-03-04 15:08:37.183817+0800 Test[68879:4182944] 执行任务3,当前线程{number = 5, name = (null)}
2020-03-04 15:08:37.183878+0800 Test[68879:4183023] 执行任务4,当前线程{number = 6, name = (null)}

2.3自定义操作

我们使用自定义继承自 NSOperation 的子类。可以通过重写 main 或者 start 方法 来定义自己的 NSOperation 对象。重写main方法比较简单,我们不需要管理操作的状态属性 isExecuting 和 isFinished。当 main 执行完返回的时候,这个操作就结束了。

先定义一个继承自 NSOperation 的子类(YYOperation),重写main方法。

- (instancetype)initWithIndes:(int)index{
    if (self = [super init]) {
        _index = index;
    }
    return self;
}
- (void)main{
    if (!self.isCancelled) {
        sleep(1);
        NSLog(@"执行任务%d, 当前线程%@", _index, [NSThread currentThread]);
    }
}

使用YYOperation

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    for (int i = 1; i < 5; i++) {
        YYOperation *operation = [[YYOperation alloc] initWithIndes:i];
        [queue addOperation:operation];
    }

运行结果:

2020-03-04 15:21:14.136486+0800 Test[69269:4204988] 执行任务2, 当前线程{number = 4, name = (null)}
2020-03-04 15:21:14.136487+0800 Test[69269:4204990] 执行任务1, 当前线程{number = 3, name = (null)}
2020-03-04 15:21:15.137329+0800 Test[69269:4204989] 执行任务3, 当前线程{number = 5, name = (null)}
2020-03-04 15:21:15.137492+0800 Test[69269:4204982] 执行任务4, 当前线程{number = 6, name = (null)}

2.4在当前线程执行任务

三种操作的使用都加入到了队列,可以看到结果都是在子线程执行的。如果直接对操作使用start方法,则会在当前线程执行。下面都用NSBlockOperation演示,其他方法结果也一样。

2.4.1在主线程调用start

for (int i = 1; i < 5; i++) {
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            sleep(1);
            NSLog(@"任务%d,当前i线程%@", i, [NSThread currentThread]);
            
        }];
        [blockOperation start];
    }
NSLog(@"主线程执行,当前线程%@",[NSThread currentThread]);

运行结果:

2020-03-04 15:29:28.535959+0800 Test[69456:4215498] 任务1,当前i线程{number = 1, name = main}
2020-03-04 15:29:29.537592+0800 Test[69456:4215498] 任务2,当前i线程{number = 1, name = main}
2020-03-04 15:29:30.539150+0800 Test[69456:4215498] 任务3,当前i线程{number = 1, name = main}
2020-03-04 15:29:31.540707+0800 Test[69456:4215498] 任务4,当前i线程{number = 1, name = main}
2020-03-04 15:29:31.540978+0800 Test[69456:4215498] 主线程执行,当前线程{number = 1, name = main}

任务在主线程同步执行,阻塞了主线程

2.4.2在子线程调用start

for (int i = 1; i < 5; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"GCD第%d次任务,子线程%@", i, [NSThread currentThread]);
            NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
                sleep(1);
                NSLog(@"任务%d,当前i线程%@", i, [NSThread currentThread]);
            }];
            [blockOperation start];
        });
    }
    NSLog(@"主线程执行,当前线程%@",[NSThread currentThread]);

运行结果:

2020-03-04 15:34:37.628751+0800 Test[69721:4226278] 主线程执行,当前线程{number = 1, name = main}
2020-03-04 15:34:37.628783+0800 Test[69721:4226318] GCD第1次任务,子线程{number = 5, name = (null)}
2020-03-04 15:34:37.628855+0800 Test[69721:4226768] GCD第2次任务,子线程{number = 6, name = (null)}
2020-03-04 15:34:37.628904+0800 Test[69721:4226770] GCD第4次任务,子线程{number = 7, name = (null)}
2020-03-04 15:34:37.628908+0800 Test[69721:4226769] GCD第3次任务,子线程{number = 8, name = (null)}
2020-03-04 15:34:38.632546+0800 Test[69721:4226769] 任务3,当前i线程{number = 8, name = (null)}
2020-03-04 15:34:38.632547+0800 Test[69721:4226768] 任务2,当前i线程{number = 6, name = (null)}
2020-03-04 15:34:38.632632+0800 Test[69721:4226318] 任务1,当前i线程{number = 5, name = (null)}
2020-03-04 15:34:38.632653+0800 Test[69721:4226770] 任务4,当前i线程{number = 7, name = (null)}

可以看到任务都在各自的子线程异步执行。
得出结论:直接调用操作的start方法,会在当前所在线程执行操作。

2.5操作可以监听他的完成状态

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作开始执行");
        sleep(1);
        NSLog(@"操作执行结束");
    }];
    [queue addOperation:operation];
    operation.completionBlock = ^{
        NSLog(@"操作完成的回调");
    };

3.操作的依赖

通过操作依赖,我们可以方便的控制操作执行的先后顺序。NSOperation提供了三个方法让我们管理和查看依赖关系,着三个方法是在基类NSOperation中的,所以都可以调用。

//当前操作依赖op,即op完成之后才能执行当前操作
- (void)addDependency:(NSOperation *)op;
//移除当前操作对op的依赖
- (void)removeDependency:(NSOperation *)op;
//查看当前操作依赖哪些操作
@property (readonly, copy) NSArray *dependencies;

3.1添加操作依赖

假设有A、B、C三个操作,A、B并发执行且都完成之后执行C,代码如下

//创建操作队列
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    //创建最后一个操作
    NSBlockOperation *AOperation=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务A开始,当前线程%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"任务A完成,当前线程%@", [NSThread currentThread]);
    }];
    [queue addOperation:AOperation];
    NSBlockOperation *BOperation=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务B开始,当前线程%@", [NSThread currentThread]);
        sleep(3);
        NSLog(@"任务B完成,当前线程%@", [NSThread currentThread]);
    }];
    [queue addOperation:BOperation];
    NSBlockOperation *COperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务C开始,当前线程%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"任务C完成,当前线程%@", [NSThread currentThread]);
    }];
    //让操作C依赖操作A
    [COperation addDependency:AOperation];
    //让操作C依赖操作B
    [COperation addDependency:BOperation];
    //将操作C加入队列
    [queue addOperation:COperation];

运行结果:

2020-03-04 17:01:33.289201+0800 Test[71832:4325629] 任务B开始,当前线程{number = 4, name = (null)}
2020-03-04 17:01:33.289201+0800 Test[71832:4325628] 任务A开始,当前线程{number = 3, name = (null)}
2020-03-04 17:01:34.291091+0800 Test[71832:4325628] 任务A完成,当前线程{number = 3, name = (null)}
2020-03-04 17:01:36.289664+0800 Test[71832:4325629] 任务B完成,当前线程{number = 4, name = (null)}
2020-03-04 17:01:36.290023+0800 Test[71832:4325628] 任务C开始,当前线程{number = 3, name = (null)}
2020-03-04 17:01:37.290728+0800 Test[71832:4325628] 任务C完成,当前线程{number = 3, name = (null)}

可以看到A、B同时执行,都完成后C才开始执行。
注意点:

  • 1.操作的依赖关系跟本身绑定,与队列无关,即一个操作可以绑定多个队列的操作。
  • 2.需要依赖的操作(C)添加到队列之前要先添加好依赖关系,否则依赖关系不生效。如果后续还想依赖其他操作,在没有执行C操作前,添加有效。实际情况很难控制,所以最好是先添加好所有依赖关系。
  • 3.被依赖的操作A、B可以在添加完C操作在添加,依赖关系仍然有效。(测试可行,个人感觉这样不太好)
    验证:操作的依赖关系跟本身绑定,与队列无关,即一个操作可以绑定多个队列的操作。
    创建操作的代码不变,把A、C加入队列1,B加入队列2
    //创建操作队列
    NSOperationQueue *queue1=[[NSOperationQueue alloc] init];
    NSOperationQueue *queue2=[[NSOperationQueue alloc] init];
    ······
    [queue1 addOperation:AOperation];
    //让操作C依赖操作A
    [COperation addDependency:AOperation];
    [queue2 addOperation:BOperation];
    //让操作C依赖操作B
    [COperation addDependency:BOperation];
    //将操作C加入队列
    [queue1 addOperation:COperation];

运行结果,操作C还是等操作A、B完成之后再执行。
验证:添加到队列之前要先添加好依赖关系,否则依赖关系不生效
把上面代码中添加依赖和C加入操作的顺序修改,其他不变

  //将操作C加入队列
  [queue addOperation:COperation];
    //让操作C依赖操作A
    [COperation addDependency:AOperation];
    //让操作C依赖操作B
    [COperation addDependency:BOperation];

运行结果:

2020-03-04 17:16:35.742407+0800 Test[72377:4350847] 任务C开始,当前线程{number = 3, name = (null)}
2020-03-04 17:16:35.742407+0800 Test[72377:4350848] 任务B开始,当前线程{number = 4, name = (null)}
2020-03-04 17:16:35.742408+0800 Test[72377:4350849] 任务A开始,当前线程{number = 5, name = (null)}
2020-03-04 17:16:36.742806+0800 Test[72377:4350849] 任务A完成,当前线程{number = 5, name = (null)}
2020-03-04 17:16:36.742848+0800 Test[72377:4350847] 任务C完成,当前线程{number = 3, name = (null)}
2020-03-04 17:16:38.743161+0800 Test[72377:4350848] 任务B完成,当前线程{number = 4, name = (null)}

原因:添加到队列之前没有添加依赖关系,所以直接被执行了。
验证:如果后续还想依赖其他操作,在没有执行C操作前,添加有效
在添加完C对B的依赖之后过2秒,添加C对A的依赖,因为B任务执行3秒,A任务执行1秒,代码修改如下。

    //让操作C依赖操作B
    [COperation addDependency:BOperation];
    //将操作C加入队列
    [queue addOperation:COperation];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [queue addOperation:AOperation];
        //让操作C依赖操作A
        [COperation addDependency:AOperation];
    });

结果:B没执行完时开始执行A,等A,B都完成时执行C。
如果改成C先依赖A,过2秒之后添加C对B的依赖,则结果为A执行完之后直接执行C,操作B两秒之后直接执行,C没有依赖B。
所以得出结论,操作没有执行之前,还是可以依赖其他操作的。
验证:被依赖的操作A、B可以在添加完C操作在添加,依赖关系仍然有效
先添加C操作,两秒之后添加A、B操作,修改代码如下:

//让操作C依赖操作A
    [COperation addDependency:AOperation];
    //让操作C依赖操作B
    [COperation addDependency:BOperation];
    //将操作C加入队列
    [queue addOperation:COperation];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [queue addOperation:AOperation];
        [queue addOperation:BOperation];
    });

结果,C仍然会等A、B执行完在执行。

3.2移除操作依赖

取消操作依赖在执行操作前随时都可以取消
添加操作部分和3.1相同,延迟2秒后取消C对B的依赖,新增部分代码如下:

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [COperation removeDependency:BOperation];
        NSLog(@"取消C操作对B操作的依赖");
    });

执行结果:

2020-03-04 17:39:28.679261+0800 Test[73092:4389857] 任务B开始,当前线程{number = 4, name = (null)}
2020-03-04 17:39:28.679260+0800 Test[73092:4389855] 任务A开始,当前线程{number = 3, name = (null)}
2020-03-04 17:39:29.684768+0800 Test[73092:4389855] 任务A完成,当前线程{number = 3, name = (null)}
2020-03-04 17:39:30.873344+0800 Test[73092:4389748] 取消C操作对B操作的依赖
2020-03-04 17:39:30.873455+0800 Test[73092:4389855] 任务C开始,当前线程{number = 3, name = (null)}
2020-03-04 17:39:31.684786+0800 Test[73092:4389857] 任务B完成,当前线程{number = 4, name = (null)}
2020-03-04 17:39:31.874800+0800 Test[73092:4389855] 任务C完成,当前线程{number = 3, name = (null)}

可以看到任务B完成前,任务C已经开始执行

4.追加操作

在NSBlockOperation下可以追加操作

//追加操作
- (void)addExecutionBlock:(void (^)(void))block;
//查看追加的操作
@property (readonly, copy) NSArray *executionBlocks;

注意点:

  • 1.追加的任务不一定在当前操作所在的线程执行,会和当前线程中的任务并发执行。
  • 2.当前操作添加到主队列,本来在主线程执行,但是追加了操作之后不一定会在主线程执行。
  • 3.追加操作最大并发数无法通过maxConcurrentOperationCount控制。

4.1追加的任务不一定在当前操作所在的线程执行,会和当前线程中的任务并发执行。

追加操作代码:

    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作开始执行 %@", [NSThread currentThread]);
        sleep(5);
        NSLog(@"操作结束执行 %@", [NSThread currentThread]);
    }];
    [queue addOperation:blockOperation];
    
    for (int i = 0; i < 5; i++) {
        [blockOperation addExecutionBlock:^{
            sleep(1);
            NSLog(@"追加操作--%d %@", i, [NSThread currentThread]);
        }];
    }

运行结果:

2020-03-04 18:02:03.074591+0800 Test[73771:4427776] 操作开始执行 {number = 3, name = (null)}
2020-03-04 18:02:04.075534+0800 Test[73771:4427777] 追加操作--1 {number = 5, name = (null)}
2020-03-04 18:02:04.075536+0800 Test[73771:4427800] 追加操作--2 {number = 4, name = (null)}
2020-03-04 18:02:04.075534+0800 Test[73771:4427504] 追加操作--0 {number = 6, name = (null)}
2020-03-04 18:02:05.076406+0800 Test[73771:4427504] 追加操作--4 {number = 6, name = (null)}
2020-03-04 18:02:05.076410+0800 Test[73771:4427777] 追加操作--3 {number = 5, name = (null)}
2020-03-04 18:02:08.077463+0800 Test[73771:4427776] 操作结束执行 {number = 3, name = (null)}

由结果可以发现追加的操作和原来的操作在不同的线程执行。

4.2把操作加到主队列,操作不在主队列执行

当前操作添加到主队列,本来在主线程执行,但是追加了操作之后不一定会在主线程执行
不追加操作查看结果

    NSOperationQueue *queue=[NSOperationQueue mainQueue];
    NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作开始执行 %@", [NSThread currentThread]);
        sleep(5);
        NSLog(@"操作结束执行 %@", [NSThread currentThread]);
    }];
    [queue addOperation:blockOperation];

运行结果:

2020-03-04 18:12:01.446205+0800 Test[74106:4444608] 操作开始执行 {number = 1, name = main}
2020-03-04 18:12:06.447706+0800 Test[74106:4444608] 操作结束执行 {number = 1, name = main}

操作在主线程执行
追加操作:

for (int i = 0; i < 5; i++) {
        [blockOperation addExecutionBlock:^{
            sleep(1);
            NSLog(@"追加操作--%d %@", i, [NSThread currentThread]);
        }];
    }

运行结果:
运行多次,该操作和追加的操作会在主线程和其他线程并发执行,并不是每次都有主线程。

4.3该操作在当前线程执行,然后追加操作

当前操作不管是添加到主队列执行还是调用start方法在当前线程执行,追加的操作都会异步执行(在其他线程执行)
先不添加操作

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"当前线程%@", [NSThread currentThread]);
        NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"操作开始执行 %@", [NSThread currentThread]);
            sleep(5);
            NSLog(@"操作结束执行 %@", [NSThread currentThread]);
        }];
        [blockOperation start];
    });

运行结果:

2020-03-04 18:20:09.322738+0800 Test[74415:4458859] 当前线程{number = 3, name = (null)}
2020-03-04 18:20:09.323237+0800 Test[74415:4458859] 操作开始执行 {number = 3, name = (null)}
2020-03-04 18:20:14.328537+0800 Test[74415:4458859] 操作结束执行 {number = 3, name = (null)}

追加操作:
把追加操作放入子线程崩溃,猜测追加操作必须在主线程添加。

5.阻塞

5.1操作阻塞

//阻塞当前线程,直到该操作完成才可以继续执行。
- (void)waitUntilFinished;

5.1.1阻塞主线程

NSOperationQueue *queue=[[NSOperationQueue alloc]init];
    
    NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作开始执行%@", [NSThread currentThread]);
        sleep(3);
        NSLog(@"操作执行完毕%@", [NSThread currentThread]);
    }];
    [queue addOperation:operation];
    NSLog(@"操作阻塞主线程");
    [operation waitUntilFinished];
    NSLog(@"主线程阻塞结束");

运行结果:“主线程阻塞结束”要等操作执行完毕才打印。因为“[operation waitUntilFinished];”是在主线程调用的。

5.1.2阻塞其他队列中的线程

NSOperationQueue *queue1=[[NSOperationQueue alloc]init];
    NSOperationQueue *queue2=[[NSOperationQueue alloc]init];
    
    NSBlockOperation *operationA=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作A开始执行%@", [NSThread currentThread]);
        sleep(2);
        NSLog(@"操作A执行完毕%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationB=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"阻塞操作B的线程");
        [operationA waitUntilFinished];
        NSLog(@"操作B开始执行%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"操作B执行完毕%@", [NSThread currentThread]);
    }];
    [queue1 addOperation:operationA];
    [queue2 addOperation:operationB];

    NSLog(@"主线程");

运行结果:

2020-03-05 15:17:07.502462+0800 Test[88963:4947037] 主线程
2020-03-05 15:17:07.502463+0800 Test[88963:4949187] 阻塞操作B的线程
2020-03-05 15:17:07.502550+0800 Test[88963:4947280] 操作A开始执行{number = 3, name = (null)}
2020-03-05 15:17:09.505609+0800 Test[88963:4947280] 操作A执行完毕{number = 3, name = (null)}
2020-03-05 15:17:09.506117+0800 Test[88963:4949187] 操作B开始执行{number = 4, name = (null)}
2020-03-05 15:17:10.509934+0800 Test[88963:4949187] 操作B执行完毕{number = 4, name = (null)}

操作B等待操作A完成才开始执行。

5.1.3尽量避免用同一个队列的操作去阻塞同一队列其他操作的线程

有可能会造成操作死锁,如果最大并发数不是1则不一定

An operation object must never call this method on itself and should avoid calling it on any operations submitted to the same operation queue as itself. Doing so can cause the operation to deadlock. 

验证:假设在同一个队列中有操作A和B,用A操作阻塞B操作,把当前队列的最大并发数设置为1,则会造成死锁。

NSOperationQueue *queue=[[NSOperationQueue alloc]init];
    queue.maxConcurrentOperationCount = 1;
    
    NSBlockOperation *operationA=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作A开始执行%@", [NSThread currentThread]);
        sleep(2);
        NSLog(@"操作A执行完毕%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationB=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"阻塞操作B的线程");
        [operationA waitUntilFinished];
        NSLog(@"操作B开始执行%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"操作B执行完毕%@", [NSThread currentThread]);
    }];
    [queue addOperation:operationB];
    [queue addOperation:operationA];

运行结果:

2020-03-05 15:27:45.095406+0800 Test[89272:4966538] 主线程
2020-03-05 15:27:45.095406+0800 Test[89272:4966672] 阻塞操作B的线程

之后就不执行了,故意先添加操作B,如果先添加操作A则不会死锁。
因为同一个队列加入操作B之后B开始执行,B又被A操作阻塞,需要先运行A操作,但是队列最大并发只有1,所以造成死锁。
总结:
waitUntilFinished方法可以用操作阻塞当前线程,但是不能阻塞自己,避免阻塞同一队列的其他操作。

5.2队列阻塞

- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0))

这个方法是向队列加入一组操作,第一个参数表示加入的操作数组,第二个参数表示是否在加入的操作执行时阻塞当前线程。传入YES:会阻塞当前线程。传入NO:不会阻塞,只是单纯的批量加入操作。
用法:

    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"队列中的操作执行开始,当前线程:%@", [NSThread currentThread]);
        sleep(3);
        NSLog(@"队列中的操作执行完毕");
    }];
    NSLog(@"添加操作,当前线程:%@", [NSThread currentThread]);
    [operationQueue addOperations:@[blockOperation] waitUntilFinished:YES];
    NSLog(@"添加完成,当前线程:%@", [NSThread currentThread]);

执行结果:

2020-03-05 11:48:01.504201+0800 Test[84093:4745766] 添加操作,当前线程:{number = 1, name = main}
2020-03-05 11:48:01.504531+0800 Test[84093:4746911] 队列中的操作执行开始,当前线程:{number = 3, name = (null)}
2020-03-05 11:48:04.509973+0800 Test[84093:4746911] 队列中的操作执行完毕
2020-03-05 11:48:04.510452+0800 Test[84093:4745766] 添加完成,当前线程:{number = 1, name = main}

把上面代码中的YES改为NO,执行结果:

2020-03-05 11:49:18.461367+0800 Test[84156:4749538] 添加操作,当前线程:{number = 1, name = main}
2020-03-05 11:49:18.461611+0800 Test[84156:4749538] 添加完成,当前线程:{number = 1, name = main}
2020-03-05 11:49:18.461693+0800 Test[84156:4749636] 队列中的操作执行开始,当前线程:{number = 3, name = (null)}
2020-03-05 11:49:21.465675+0800 Test[84156:4749636] 队列中的操作执行完毕

所以这个方法可以阻塞往队列添加方法时所在的队列。

注意:不可以在主线程阻塞添加到主队列的操作,会造成死锁

    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作被执行%@", [NSThread currentThread]);
    }];
    NSLog(@"添加操作");
    [queue addOperations:@[blockOperation] waitUntilFinished:YES];
    NSLog(@"添加完成");

运行可以发现只打印了“添加操作”。
因为主队列只有一个线程,当前线程被主队列的操作阻塞,主队列的操作又只能在主线程执行,所以造成了死锁。

6.操作的优先级(queuePriority)

  • 1.默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal,通过“setQueuePriority:”改变当前操作在同一队列中的执行优先级。

  • 2.依赖关系优先于优先级属性。只有准备就绪状态下的操作,才会按优先级属性执行。

  • 3.queuePriority属性使用于同一操作队列中的操作,不再同一操作队列中无效
    准备就绪概念解释:
    当一个操作的所有依赖操作都完成时,该操作通常会进入准备就绪状态,此时等待执行。

  • 6.1高优先级在没有依赖时会比低优先级先执行
    为了排除添加顺序对操作优先级的影响,使用批量添加操作。

    NSOperationQueue *operationQueue= [[NSOperationQueue alloc]init];    
//为了效果明显,设置成最大并发数为1
    operationQueue.maxConcurrentOperationCount = 1;
    NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"低优先级任务开始,当前线程:%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"低优先级任务结束");
    }];
    blockOperation1.queuePriority = NSOperationQueuePriorityLow;
    NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"高优先级任务开始,当前线程:%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"高优先级任务结束");
    }];
    blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
    [operationQueue addOperations:@[blockOperation1, blockOperation2] waitUntilFinished:NO];

执行结果:

2020-03-05 11:57:59.001612+0800 Test[84488:4764598] 高优先级任务开始,当前线程:{number = 3, name = (null)}
2020-03-05 11:58:00.004882+0800 Test[84488:4764598] 高优先级任务结束
2020-03-05 11:58:00.005194+0800 Test[84488:4764606] 低优先级任务开始,当前线程:{number = 4, name = (null)}
2020-03-05 11:58:01.007561+0800 Test[84488:4764606] 低优先级任务结束

6.2依赖关系优先于优先级属性

有三个操作A、B、C,A和C是低优先级任务,B是高优先级任务。A、B依赖任务C

    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    NSBlockOperation *operationA=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"低优先级任务A开始,当前线程:%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"低优先级任务A结束");
    }];
    operationA.queuePriority = NSOperationQueuePriorityLow;
    
    NSBlockOperation *operationB =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"高优先级任务B开始,当前线程:%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"高优先级任务B结束");
    }];
    operationB.queuePriority = NSOperationQueuePriorityHigh;
    
    NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"被依赖的操作C开始执行%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"被依赖的操作C执行结束");
    }];
    operationC.queuePriority = NSOperationQueuePriorityLow;
    
    [operationA addDependency:operationC];
    [operationB addDependency:operationC];
    
    [operationQueue addOperations:@[operationA, operationB, operationC] waitUntilFinished:NO];

运行结果:

2020-03-05 13:42:59.562305+0800 Test[86649:4842154] 被依赖的操作C开始执行{number = 3, name = (null)}
2020-03-05 13:43:00.564699+0800 Test[86649:4842154] 被依赖的操作C执行结束
2020-03-05 13:43:00.565218+0800 Test[86649:4842155] 高优先级任务B开始,当前线程:{number = 4, name = (null)}
2020-03-05 13:43:00.565292+0800 Test[86649:4842156] 低优先级任务A开始,当前线程:{number = 5, name = (null)}
2020-03-05 13:43:01.568207+0800 Test[86649:4842155] 高优先级任务B结束
2020-03-05 13:43:01.568207+0800 Test[86649:4842156] 低优先级任务A结束

等低优先级任务C完成后,开始执行B,由于没有设置最大并发数,所以A也马上执行。

6.3优先级关系要在同一队列才有效果。

    NSOperationQueue *queueA = [[NSOperationQueue alloc] init];
    NSOperationQueue *queueB = [[NSOperationQueue alloc] init];
    NSOperationQueue *queueC = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *operationA=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"低优先级任务A开始,当前线程:%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"低优先级任务A结束");
    }];
    operationA.queuePriority = NSOperationQueuePriorityLow;
    
    NSBlockOperation *operationB =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"高优先级任务B开始,当前线程:%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"高优先级任务B结束");
    }];
    operationB.queuePriority = NSOperationQueuePriorityHigh;
    
    NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"被依赖的操作C开始执行%@", [NSThread currentThread]);
        sleep(1);
        NSLog(@"被依赖的操作C执行结束");
    }];
    operationC.queuePriority = NSOperationQueuePriorityLow;
    
    [queueC addOperation:operationC];
    [operationA addDependency:operationC];
    [operationB addDependency:operationC];
    
    [queueA addOperation:operationA];
    [queueA addOperation:operationB];

运行结果:被依赖的任务C先运行完之后,A和B谁先开始不固定,因为在不同队列,所以是并发执行的,谁先开始都有可能。
让A和B都依赖C,是为了让A、B同时进入准备就绪的状态。把依赖任务C加入C队列是为了排除干扰。

7.操作的取消

在NSOperation中可以调用操作的-cancel取消不是正在执行的操作。

    [operation cancel];

取消队列中的所有操作

  //只能取消所有队列的里面的操作,正在执行的无法取消
  //取消操作并不会影响队列的挂起状态
  [self.opQueue cancelAllOperations];
  NSLog(@"取消队列里所有的操作");
  //取消队列的挂起状态
  //(只要是取消了队列的操作,我们就把队列处于不挂起状态,以便于后续的开始)
  self.opQueue.suspended = NO;

线程的挂起

 //判断操作的数量,当前队列里面是不是有操作?
  if (self.opQueue.operationCount == 0) {
    NSLog(@"当前队列没有操作");
    return;
  }

  self.opQueue.suspended = !self.opQueue.isSuspended;
  if (self.opQueue.suspended) {
    NSLog(@"暂停");
  }else{
    NSLog(@"继续");
  }

你可能感兴趣的:(NSOperation)