NSOperation是iOS中实现多线程的一个重要模块,在这里整理一些容易忽略或者模糊的点,不会有太多基础的知识。

先大致介绍一些基本知识。

核心的两个类 NSOperation 和 NSOperationQueue

概述

NSOperation可以理解为待执行的任务。

NSOperationQueue是保存任务的队列。

NSOperation

使用NSOperation一共有三种方式

  • NSBlockOperation(block形式)
  • NSInvocationOperation(selector形式)
  • 继承NSOperation

第一种就是用block的方式实现需要执行的任务;第二种就是使用selector的方式;第三种,由于NSOperation本身是一个基类,内部主要实现operation状态、控制等,没有提供任务接口,所以需要自己继承。

NSOperationQueue

使用NSOperationQueue一共有两种方式

  • 主队列,[NSOperationQueue mainQueue]
  • 其他队列,[[NSOperationQueue alloc] init]

第一种队列加入的任务默认是在主线程中执行,由于默认的最大并发数 maxConcurrentOperationCount = 1,所以在主队列中执行的任务只能是串行执行;第二种自行init的队列默认并发数是-1,所以在该种队列中的任务会并发执行。


下面到了重点了。(由于方便比较结果,所以下面都统一用 NSBlockOperation 来演示)

NSBlockOperation 中有两个方法来增加任务,一个是默认的构造方法 NSBlockOperation blockOperationWithBlock;另一个是用来增加任务的 addExecutionBlock。那么问题来了,通过addExecutionBlock增加的任务到底是按照什么方式执行的呢?在不同的队列(主队列、其他队列)中会不会有不同呢?多个队列之间的执行又是什么样的呢?

下面就通过几个例子来说明这些问题

1. addExecutionBlock的任务是串行还是并行执行的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSBlockOperation *a = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"a------%@", [NSThread currentThread]);
}];
[a addExecutionBlock:^{
NSLog(@"a1------%@", [NSThread currentThread]);
}];
[a addExecutionBlock:^{
NSLog(@"a2------%@", [NSThread currentThread]);
}];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:a];

输出有多种情况

可以看出,虽然add在了主队列中,同一个operation中的不同任务(这里可以叫做execution)不一定是串行的。通过addExecutionBlock的execution有可能是在其他线程中执行,也可能是在主线程中执行,但是一定会有一个execution在主线程中执行。

2. mainQueue中的多个operation是怎么执行的?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
NSLog(@"------------ab---------------");
NSBlockOperation *a = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"a------%@", [NSThread currentThread]);
}];
NSBlockOperation *b = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"b------%@", [NSThread currentThread]);
}];
[a addExecutionBlock:^{
NSLog(@"a1------%@", [NSThread currentThread]);
}];
[a addExecutionBlock:^{
NSLog(@"a2------%@", [NSThread currentThread]);
}];
[b addExecutionBlock:^{
NSLog(@"b1------%@", [NSThread currentThread]);
}];
[b addExecutionBlock:^{
NSLog(@"b2------%@", [NSThread currentThread]);
}];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:a];
[mainQueue addOperation:b];

可以看出,主队列中add的不同operation是串行执行的,但是每个operation内部多个不同execution仍然是并行的。

所以通过前两个例子可以充分了解 maxConcurrentOperationCount = 1 时所谓的串行是指队列中多个operation之间是串行的,但每个operation之间不同的execution仍然是并行的。

由于其他队列都是并发的,情况比较容易理解,所以就不再单独讲其他队列的情况

3. 多个队列的执行情况

其实多个队列的执行情况也容易理解,多个队列之间都是不相干的(没有dependency的情况下),所以就是单纯的并发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
NSBlockOperation *a = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"a------%@", [NSThread currentThread]);
}];
NSBlockOperation *b = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"b------%@", [NSThread currentThread]);
}];
[a addExecutionBlock:^{
NSLog(@"a1------%@", [NSThread currentThread]);
}];
[a addExecutionBlock:^{
NSLog(@"a2------%@", [NSThread currentThread]);
}];
NSBlockOperation *c = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"c------%@", [NSThread currentThread]);
}];
[b addExecutionBlock:^{
NSLog(@"b1------%@", [NSThread currentThread]);
}];
[b addExecutionBlock:^{
NSLog(@"b2------%@", [NSThread currentThread]);
}];
[c addExecutionBlock:^{
NSLog(@"c1------%@", [NSThread currentThread]);
}];
[c addExecutionBlock:^{
NSLog(@"c2------%@", [NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[queue addOperation:c];
[queue addOperation:b];
[mainQueue addOperation:a];


可以看到,所有任务之间都是单纯的并发,不会存在不同类型队列之间的依赖关系(就是不会有一定先执行主队列,再执行其他队列的情况。但优先级可能会有不同,这个不确定,多次打log发现大部分情况第一个输出的还是主队列的一个任务)