浅谈iOS多线程_使用篇

0x01 老生常谈

  众所周知,多线程的合理使用可以提高CPU利用率,在实际开发中最常见的就是涉及大量计算以及网络请求的时候,都会新开一个线程,避免主线程被阻塞让用户觉得app卡顿。但凡事都不能过犹不及,滥用多线程也会给性能造成影响,毕竟线程之间的切换也是需要花费开销的。

  既然叫多线程,那么线程之间肯定是有优先级区别的,在iOS中,有这么几种权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef NS_ENUM(NSInteger, NSQualityOfService) {
// 用户交互权限,属于最高等级,常被用于处理交互事件或者刷新UI,因为这些需要即时的
NSQualityOfServiceUserInteractive = 0x21,

// 为了可以进一步的后续操作,当用户发起请求结果需要被立即展示,比如当点了列表页某条信息后需要立即加载详情信息
NSQualityOfServiceUserInitiated = 0x19,

// 不需要马上就能得到结果,比如下载任务。当资源被限制后,此权限的任务将运行在节能模式下以提供更多资源给更高的优先级任务
NSQualityOfServiceUtility = 0x11,

// 后台权限,通常用户都不能意识到有任务正在进行,比如数据备份等。大多数处于节能模式下,需要把资源让出来给更高的优先级任务
NSQualityOfServiceBackground = 0x09,

// 默认权限,具体权限由系统根据实际情况来决定使用哪个等级权限,如果实际情况不太利于决定使用何种权限,则从UserInitiated和Utility之间选一个权限并使用。
NSQualityOfServiceDefault = -1
}

0x02 使用多线程

  在iOS中,实现多线程常用的有三种方式:NSThread、GCD和NSOperation;以及一个不怎么常用的pthread。

1. NSThread

1.1 如何使用

  加上iOS10新增的,现在有5种初始化方法,具体初始化代码这边就不放了,实例方法中非block初始化的需要调用start方法进行线程开启:

1
2
3
4
5
+ (void)detachNewThreadWithBlock:(void (^)(void))block; // iOS 10 新增
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
- (instancetype)initWithBlock:(void (^)(void))block; // iOS 10 新增
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
- (instancetype)init;

  苹果更是为了程序员更方便的调用,直接在NSObject写了扩展,我们只要[self perform….]这样的调用,就能轻松开启一个线程。

  在iOS 8开始,苹果给NSThread提供了qualityOfService属性来设置优先级,比如:

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
- (void)viewDidLoad {
[super viewDidLoad];

[NSThread detachNewThreadWithBlock:^{
NSLog(@"线程1开始执行。。。");
}];

[NSThread detachNewThreadSelector:@selector(thread2Selector:) toTarget:self withObject:@"test"];

NSThread *thread3 = [[NSThread alloc] initWithBlock:^{
NSLog(@"线程3开始执行。。。");
}];

NSThread *thread4 = [[NSThread alloc] initWithTarget:self selector:@selector(thread4Selector:) object:@"test"];
thread4.qualityOfService = NSQualityOfServiceUserInteractive;

[thread3 start];
[thread4 start];
}

- (void)thread2Selector:(id)info {
NSLog(@"线程2开始执行。。。");
}

- (void)thread4Selector:(id)info {
NSLog(@"线程4开始执行。。。");
}

  运行结果很清楚的看到,我设置了最高等级的线程4优先执行:

1
2
3
4
2018-01-02 16:05:09.599964+0800 HotPatch[54134:37810941] 线程4开始执行。。。
2018-01-02 16:05:09.601452+0800 HotPatch[54134:37810939] 线程2开始执行。。。
2018-01-02 16:05:09.602699+0800 HotPatch[54134:37810938] 线程1开始执行。。。
2018-01-02 16:05:09.602698+0800 HotPatch[54134:37810940] 线程3开始执行。。。

  其他属性这里不做着重说明,API都有的,也都很好理解。

1.2 总结

  我们可以看到,NSThread适用于业务场景不是很复杂的时候,属于比较轻量级的,同时在NSThread进行了相关拓展,所以开发中调用也极其方便。同时缺点也很明显,不能设置线程之间的依赖关系,需要手动管理睡眠唤醒等。


2. Grand Central Dispatch (GCD)

  GCD是C实现的,所以效率相对更高,可以更好更方便的让并行代码执行在多核设备上,而且GCD是系统级的,帮助程序可以更合理的利用可用资源。跟NSThread相比,在使用GCD时我们并不需要手动管理任务,我们只需要把任务塞到队列里,系统的线程池会自动帮我们运行和销毁等。在ARC下,GCD的内存管理跟其他对象一样,在非ARC下,需要通过dispatch_retain和dispatch_release进行管理。
  详细说GCD之前,我们先说说下面几个概念:并行、串行、同步和异步。

  • 串行:任务是一个一个有顺序的执行,一个执行完以后才执行下一个。(有序的)
  • 并行:跟串行相反,任务是无序的执行,执行顺序没有顺序关系。(无序的)
  • 同步:需要等上一个任务执行完成后才能执行下一个任务。(依赖于上个任务是否执行完毕)
  • 异步:不需要等到上一个任务执行完成才执行下一个任务。(无需依赖于上个任务是否执行完毕)

  在GCD里,我们通过DISPATCH_QUEUE_SERIALDISPATCH_QUEUE_CONCURRENT分别表示串行和并行;通过dispatch_syncdispatch_async分别表示同步和异步。它们之间互相结合会碰撞出什么样的火花?我们下面通过代码看下结果:

① 串行同步

1
2
3
4
5
6
7
8
9
10
11
12
13
// 首先是串行同步的情况。
dispatch_queue_t serailQueue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serailQueue, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_sync(serailQueue, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_sync(serailQueue, ^{
NSLog(@"%@主线程上,第三只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

  根据运行结果,串行同步的任务是在主线程上一个一个有顺序的完成的。

1
2
3
2018-01-03 09:22:59.451187+0800 HotPatch[84240:38942350] 在主线程上,第一只熊猫向你走过来...
2018-01-03 09:22:59.451341+0800 HotPatch[84240:38942350] 在主线程上,第二只熊猫向你走过来...
2018-01-03 09:22:59.451450+0800 HotPatch[84240:38942350] 在主线程上,第三只熊猫向你走过来...

  接着,我们观察下串行异步的结果会是如何?

② 串行异步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_queue_t serailQueue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serailQueue, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_async(serailQueue, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_async(serailQueue, ^{
NSLog(@"%@主线程上,第三只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

NSLog(@"%@主线程上,熊猫妈妈在找熊猫宝宝...", [NSThread isMainThread] ? @"在" : @"非");

  根据结果,我们可以看到,串行异步的情况下,新开辟了一条线程,该线程上的执行任务的顺序是有序的,而主线程上的任务是与该线程是并行执行的。

1
2
3
4
2018-01-03 09:24:04.200707+0800 HotPatch[84293:38945063] 在主线程上,熊猫妈妈在找熊猫宝宝...
2018-01-03 09:24:04.200707+0800 HotPatch[84293:38945539] 非主线程上,第一只熊猫向你走过来...
2018-01-03 09:24:04.200874+0800 HotPatch[84293:38945539] 非主线程上,第二只熊猫向你走过来...
2018-01-03 09:24:04.200969+0800 HotPatch[84293:38945539] 非主线程上,第三只熊猫向你走过来...

③ 并行同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_sync(concurrentQueue, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_sync(concurrentQueue, ^{
NSLog(@"%@主线程上,第三只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

NSLog(@"%@主线程上,熊猫妈妈在找熊猫宝宝...", [NSThread isMainThread] ? @"在" : @"非");

  根据结果来看,虽然是在并行队列上,但是并没有新开线程,都是在主线程上按顺序来执行任务。

1
2
3
4
2018-01-03 09:30:56.633674+0800 HotPatch[84466:38953980] 在主线程上,第一只熊猫向你走过来...
2018-01-03 09:30:56.633827+0800 HotPatch[84466:38953980] 在主线程上,第二只熊猫向你走过来...
2018-01-03 09:30:56.633913+0800 HotPatch[84466:38953980] 在主线程上,第三只熊猫向你走过来...
2018-01-03 09:30:56.633992+0800 HotPatch[84466:38953980] 在主线程上,熊猫妈妈在找熊猫宝宝...

④ 并行异步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_async(concurrentQueue, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_async(concurrentQueue, ^{
NSLog(@"%@主线程上,第三只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

NSLog(@"%@主线程上,熊猫妈妈在找熊猫宝宝...", [NSThread isMainThread] ? @"在" : @"非");

  根据结果来看,并行异步下,会开启多个线程执行任务,他们之间的顺序包括主线程是无序的。

1
2
3
4
2018-01-03 09:36:48.246392+0800 HotPatch[84632:38962053] 非主线程上,第二只熊猫向你走过来...
2018-01-03 09:36:48.246392+0800 HotPatch[84632:38962045] 非主线程上,第一只熊猫向你走过来...
2018-01-03 09:36:48.246392+0800 HotPatch[84632:38961995] 在主线程上,熊猫妈妈在找熊猫宝宝...
2018-01-03 09:36:48.246392+0800 HotPatch[84632:38962046] 非主线程上,第三只熊猫向你走过来...

  前面我们已经说过,串行异步,会新开一个线程,该线程里的任务按顺序执行,那么,我新建了多个串行队列,那么他们之间的关系是怎么样的?是不是也是按照顺序排排坐吃果果呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue1, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_queue_t serialQueue2 = dispatch_queue_create("com.zhaomu.test2", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue2, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_queue_t serialQueue3 = dispatch_queue_create("com.zhaomu.test3", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue3, ^{
NSLog(@"%@主线程上,第三只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});
dispatch_async(serialQueue3, ^{
NSLog(@"%@主线程上,第四只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

NSLog(@"%@主线程上,熊猫妈妈在找熊猫宝宝...", [NSThread isMainThread] ? @"在" : @"非");

  根据下面的运行结果可以看到开了多个串行队列的情况下,多个串行队列之间可能是并行执行的。

1
2
3
4
5
2018-01-03 10:09:58.754283+0800 HotPatch[85801:39008283] 在主线程上,熊猫妈妈在找熊猫宝宝...
2018-01-03 10:09:58.754287+0800 HotPatch[85801:39008328] 非主线程上,第二只熊猫向你走过来...
2018-01-03 10:09:58.754287+0800 HotPatch[85801:39008330] 非主线程上,第一只熊猫向你走过来...
2018-01-03 10:09:58.754287+0800 HotPatch[85801:39008329] 非主线程上,第三只熊猫向你走过来...
2018-01-03 10:09:58.754514+0800 HotPatch[85801:39008329] 非主线程上,第四只熊猫向你走过来...

  至此,他们四者之间的关系大致搞清楚了,那么这里遗留一个问题,下面的代码会有什么问题?为什么会导致这样的问题?

1
2
3
4
5
6
7
8
9
10
dispatch_queue_t serialQueue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");

dispatch_sync(serialQueue, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});
});

NSLog(@"%@主线程上,熊猫妈妈在找熊猫宝宝...", [NSThread isMainThread] ? @"在" : @"非");

2.1 创建队列

  之前在了解串行、并行、同步和异步的时候,已经看到可以通过dispatch_queue_create创建一个队列。那么还有其他方法吗?相信大家已经很快想到了:

1
2
3
4
5
6
7
8
dispatch_async(dispatch_get_main_queue(), ^{

});

// 或者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

});

  对于这两个方法,也没有什么特别要讲的,相信大家在实际开发中也已经用了很多很多了。只是提一点,dispatch_get_global_queue得到的队列, dispatch_suspend,dispatch_resume和dispatch_set_context函数对其是无效的,具体的后面再详细说明。
  回到dispatch_queue_create方法,之前只是说了可以设置参数来控制返回的是串行还是并行队列,但是如果我想对这个队列进行优先级设置,我们该怎么做呢?

1
2
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_USER_INTERACTIVE, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);

  dispatch_queue_attr_make_with_qos_class函数可以设置队列优先级,它的第二个参数设置优先级。这里的优先级有这几个选择,也有其对应的宏定义。具体解释可以对应本文开头的优先级解释:

  • QOS_CLASS_USER_INTERACTIVE (DISPATCH_QUEUE_PRIORITY_HIGH)
  • QOS_CLASS_USER_INITIATED (DISPATCH_QUEUE_PRIORITY_HIGH)
  • QOS_CLASS_UTILITY (DISPATCH_QUEUE_PRIORITY_LOW)
  • QOS_CLASS_DEFAULT (DISPATCH_QUEUE_PRIORITY_DEFAULT)
  • QOS_CLASS_BACKGROUND (DISPATCH_QUEUE_PRIORITY_BACKGROUND)

  dispatch_queue_attr_make_with_qos_class函数的第三个参数,需要填写一个负数的偏移值,小于0且大于等于-15(QOS_MIN_RELATIVE_PRIORITY即表示为-15),必须这么填,不然函数会返回一个null。这个参数主要作用是在你给定的优先级系统不能满足的情况下,如果需要调度的话,给定一个调度偏移值。

  当然,我们还可以通过dispatch_set_target_queue给目标队列设置优先级,比如设置权限前,我们的代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_queue_t queue1 = dispatch_get_global_queue(NSQualityOfServiceUserInitiated, 0);
dispatch_async(queue1, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_queue_t queue2 = dispatch_get_global_queue(NSQualityOfServiceUserInteractive, 0);
dispatch_async(queue2, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

// 运行结果:
2018-01-03 10:51:55.094364+0800 HotPatch[87483:39063303] 非主线程上,第二只熊猫向你走过来...
2018-01-03 10:51:55.094374+0800 HotPatch[87483:39063315] 非主线程上,第一只熊猫向你走过来...

  将第二个队列权限设置为第一个队列一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t queue1 = dispatch_get_global_queue(NSQualityOfServiceUserInitiated, 0);
dispatch_async(queue1, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_queue_t queue2 = dispatch_get_global_queue(NSQualityOfServiceUserInteractive, 0);
dispatch_async(queue2, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_set_target_queue(queue2, queue1);

// 运行结果:
2018-01-03 10:54:54.485234+0800 HotPatch[87580:39067563] 非主线程上,第一只熊猫向你走过来...
2018-01-03 10:54:54.485234+0800 HotPatch[87580:39067564] 非主线程上,第二只熊猫向你走过来...

  dispatch_set_target_queue除了可以设置优先级,同样可以设置队列类型,比如如下面的代码,其结果因为可以多开线程,所以其运行顺序是无序的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue2, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});
dispatch_async(queue2, ^{
NSLog(@"%@主线程上,第三只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

NSLog(@"%@主线程上,熊猫妈妈在找熊猫宝宝...", [NSThread isMainThread] ? @"在" : @"非");

// 运行结果
2018-01-03 11:03:07.245436+0800 HotPatch[87849:39079716] 非主线程上,第二只熊猫向你走过来...
2018-01-03 11:03:07.245431+0800 HotPatch[87849:39079659] 在主线程上,熊猫妈妈在找熊猫宝宝...
2018-01-03 11:03:07.245436+0800 HotPatch[87849:39079717] 非主线程上,第一只熊猫向你走过来...
2018-01-03 11:03:07.245436+0800 HotPatch[87849:39079714] 非主线程上,第三只熊猫向你走过来...

  现在我的需求是,队列1执行完后,我再去执行队列2,可以修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test1", DISPATCH_QUEUE_CONCURRENT);

dispatch_set_target_queue(queue2, queue1);

dispatch_async(queue1, ^{
NSLog(@"%@主线程上,第一只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

dispatch_async(queue2, ^{
NSLog(@"%@主线程上,第二只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});
dispatch_async(queue2, ^{
NSLog(@"%@主线程上,第三只熊猫向你走过来...", [NSThread isMainThread] ? @"在" : @"非");
});

NSLog(@"%@主线程上,熊猫妈妈在找熊猫宝宝...", [NSThread isMainThread] ? @"在" : @"非");

// 运行结果
2018-01-03 11:20:16.703801+0800 HotPatch[88657:39110595] 在主线程上,熊猫妈妈在找熊猫宝宝...
2018-01-03 11:20:16.703801+0800 HotPatch[88657:39110643] 非主线程上,第一只熊猫向你走过来...
2018-01-03 11:20:16.703987+0800 HotPatch[88657:39110643] 非主线程上,第二只熊猫向你走过来...
2018-01-03 11:20:16.704185+0800 HotPatch[88657:39110643] 非主线程上,第三只熊猫向你走过来...

  需要注意的是,这里dispatch_set_target_queue设置时机是设置block任务之前,同时不能循环设置,比如上面把queue1设置给了queue2,如果在queue2设置给queue1,这样会导致问题。

2.2 设置队列标识

  通过dispatch_queue_set_specific可以设置队列标识,然后通过dispatch_get_specific来帮助我们判断是否在某个队列中。其中参数key是个标识符,用来绑定所对应队列的上下文环境,举个例子:

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
static const void *key = "key";

- (void)viewDidLoad {
[super viewDidLoad];

dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, key, &key, NULL);
dispatch_async(queue1, ^{
[self performSelector:@selector(testSelector) withObject:nil];
});

dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue2, ^{
[self performSelector:@selector(testSelector2) withObject:nil];
});
}

- (void)testSelector {
if(dispatch_get_specific(key)) {
NSLog(@"这个key所属的队列是queue1队列");
}else{
NSLog(@"这个key所属的队列不是queue1队列");
}
}

- (void)testSelector2 {
if(dispatch_get_specific(key)) {
NSLog(@"这个key所属的队列是queue2队列");
}else{
NSLog(@"这个key所属的队列不是queue2队列");
}
}

// 打印结果
2018-01-03 14:02:16.396954+0800 HotPatch[92482:39270353] 这个key所属的队列不是queue2队列
2018-01-03 14:02:16.396960+0800 HotPatch[92482:39270355] 这个key所属的队列是queue1队列

  看了api可能会有疑问,dispatch_queue_get_specific和dispatch_get_specific,有什么区别?

1
2
void * dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void * dispatch_get_specific(const void *key);

  前者比后者多传了一个队列参数,所以显而易见,前者判断的是所传队列的上下文数据是不是绑定为标识符为key的;而后者则是判断当前所在上下文环境是不是标识符为key的。
  细心的朋友可能会问,既然是标识符,那api里还有个dispatch_queue_get_label是干嘛用的?我们之前调用dispatch_queue_create函数的时候,第一个参数就是label。

1
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", DISPATCH_QUEUE_SERIAL);

  这个label,可以在debug的时候,用于显示当前线程所在队列的名称,如下图:线程label示例
  那么label还有什么用处?相信肯定有的人唰唰唰写出如下代码,当然这也不是说不可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad {
[super viewDidLoad];

dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{
[self performSelector:@selector(testSelector) withObject:nil];
});
}

- (void)testSelector {
NSString *labelName = [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
if ([labelName isEqualToString:@"com.zhaomu.test1"]) {
NSLog(@"现在所处queue1队列");
}
}

  那么问题又来了,如何判断当前是否是主线程,相信很快有人会回答[NSThread isMainThread]!!!那么,我们知道一个主线程可以有多个队列,那么如何判断是否在主队列呢?
  这里介绍两种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 第一种方法
- (void)viewDidLoad {
[super viewDidLoad];

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_set_specific(mainQueue, key, &key, NULL);
dispatch_async(mainQueue, ^{
[self performSelector:@selector(testSelector) withObject:nil];
});
}

- (void)testSelector {
if(dispatch_get_specific(key)) {
NSLog(@"注意,这里是主线程主队列!!!");
}
}

// 第二种方法
NSString *currentLabel = [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
NSString *mainLabel = [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())];
if([currentLabel isEqualToString:mainLabel]) {
NSLog(@"注意,这里是主线程主队列!!!");
}

2.3 dispatch_after

  这个函数,大家肯定不陌生,用的也肯定滚瓜烂熟。这个函数的作用就是倒计时的时间到了后,将block任务添加到指定的队列里面去,然后执行。

1
2
3
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 1秒后去做的事情
});

  复习时间到了,那么我给出如下代码,dispatch_after里面的代码会在1秒后执行吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"本次操作需要2秒,操作完成");
});

dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"本次操作需要5秒,操作完成");
});

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
// 1秒后去做的事情
NSLog(@"1秒后能来到这吗");
});

  答案是并不会,我们前面说过,并行同步是排排坐吃果果的,是按顺序执行任务的,dispatch_after一秒后的确把任务提交到队列了,但是因为前面的任务还没有完成,所以它也就只能乖乖等着了。
  谈到延时执行,肯定大家还会想到另一个方法:

1
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

  那么这个方法跟dispatch_after又有什么区别呢?观察下面的代码,我的疑问就是熊猫宝宝会向您奔来吗,也就是会不会执行testSelector方法?

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
[super viewDidLoad];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelector:@selector(testSelector) withObject:nil afterDelay:2];
});
}

- (void)testSelector {
NSLog(@"一只熊猫宝宝向您奔来....");
}

  答案是否定的,方法并没有执行。这是为什么呢?performSelector:afterDelay方法内部其实有个NSTimer定时器,需要把定时器加到runloop进行执行,所以这个方法需要依赖runloop,那么我们必须知道的是,开启的子线程默认是没有runloop的,所以导致testSelector方法没有被执行。而dispatch_after并没有这个问题。
  拓展一下,GCD中除了dispatch_after,还有一种类似于NSTimer的计时器,也就是通过dispatch_source来设置定时器。其实如果看源码dispatch_after内部也是通过dispatch_source进行实现的。这里只是先做介绍,后面到dispatch_source章节再详细说明。

2.4 dispatch_once

  这个函数常用于构造一份唯一的实例,用于app内存域中达到资源共享。所以有时候我们看到一个项目里会有很多单例。那么,我们是否思考过,真的有必要在app里使用很多的单例,那么如果使用不当会造成什么后果?

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
- (void)viewDidLoad {
[super viewDidLoad];

[self once];
}

- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self once];
});
NSLog(@"遇到一只熊猫宝宝...");
}

// 或者
- (void)viewDidLoad {
[super viewDidLoad];

[self once];
}

- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self otherOnce];
});
NSLog(@"遇到第一只熊猫宝宝...");
}

- (void)otherOnce {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self once];
});
NSLog(@"遇到第二只熊猫宝宝...");
}

  我们看下栈的追踪,可以发现死锁了,具体原因在下篇GCD源码篇具体说明。dipatch_once示例
  除了单例这种方式,是否还有别的方式能达到资源共享?在Xcode 8开始,支持了类属性:

1
@property(nonatomic, copy, class) NSString *name;

  所以,我们可以利用这个新的特性,来完成资源共享。

2.5 dispatch_block

  dipatch_block,可以理解为一个block对象,拿到这个对象可以让我们独立操作一个任务,我们可以更灵活的操作一个任务,比如等待、执行以及监听任务的完成,以便在这个任务完成后去做些什么。
  一般来说,我们可以这样定义一个GCD的block对象。

1
2
3
4
5
6
7
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_block_t block = ^{
NSLog(@"一只熊脑宝宝向你奔来...");
};

dispatch_async(queue, block);

2.5.1 dispatch_block_create

  当然,一般开发场景下并不需要这么啰嗦的实现方式,没必要把block单独拿出来。而且需要注意的是,上面的声明方式,这个block是在栈上的。我们还有一种初始化block对象的方式,但是说这个之前,我们先了解下面枚举值的意思:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef enum : unsigned long {
// 当提交到DISPATCH_QUEUE_CONCURRENT队列,类似于dispatch_barrier_async(后面会介绍)作用。如果标记为这个的block对象被直接调用,将没有barrier效果。
DISPATCH_BLOCK_BARRIER,
// block对象将解除与当前执行上下文属性的关联,如果直接调用,在分配给block属性之前,在调用线程上block对象将在block任务执行期间移出这些属性。如果提交到队列,block对象将使用队列属性或者分配给Block对象的属性。
DISPATCH_BLOCK_DETACHED,
// Block对象被创建的同时会为block对象分配执行上下文属性。如果直接调用,block对象将在block任务执行期间将这些属性应用于调用线程。如果block任务被提交到队列,则这个标识将在提交队列的同时会替换其所关联的block对象默认的上下文属性。
DISPATCH_BLOCK_ASSIGN_CURRENT,
// 表示不能设置优先级属性给block,如果block被直接调用,将会使用当前线程的优先级。如果被提交到队列,在提交到队列的同时将会取消原来的优先级属性。在dispatch_block_create_with_qos_class函数中,这个属性无效。
DISPATCH_BLOCK_NO_QOS_CLASS,
// block和队列同时有优先级属性的情况下,优先使用队列的优先级。当队列没有优先级属性的情况下,block的优先级才会被采用,当block被执行调用,这个属性无效;如果被提交到并行异步队列,这个属性是默认的。
DISPATCH_BLOCK_INHERIT_QOS_CLASS。
// block的优先级属性要高于队列的优先级属性。如果block被直接调用或被提交到并行同步队列,这个属性是默认的
DISPATCH_BLOCK_ENFORCE_QOS_CLASS
} dispatch_block_flags_t;

  dispatch_block_create也可以创建一个block对象,并且重点是分配在堆上的,在创建 的时候,可以设置一个标识位,就是上面说的那几个。
  创建出来的block提交到队列的时候同时会为block赋值一个默认的优先级属性,但也有例外,这三个标识位就不会默认设置优先级,分别是DISPATCH_BLOCK_ASSIGN_CURRENT、DISPATCH_BLOCK_NO_QOS_CLASS和DISPATCH_BLOCK_DETACHED。
  当Block队列放入并行同步队列,DISPATCH_BLOCK_ENFORCE_QOS_CLASS 是默认的。当被放入并行异步队列,DISPATCH_BLOCK_INHERIT_QOS_CLASS是默认的。如果一个被赋值了优先级属性的block对象被放入到一个串行队列,那么系统将会尽可能的让已经在前面的block对象与这个block对象拥有一个优先级或者更高优先级,以让之前的block任务优先执行。
  前面说到可以给block设置优先级,所以先介绍下dispatch_block_create_with_qos_class函数,跟之前dispatch_block_create相比,多了一个dispatch_qos_class_t属性,用来设置优先级,以及relative_priority属性,表示偏移值,这个参数主要作用是在你给定的优先级系统不能满足的情况下,如果需要调度的话,给定一个调度偏移值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", attr_t);
dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test2", attr_t);
dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
});
dispatch_block_t block2 = dispatch_block_create_with_qos_class(DISPATCH_BLOCK_ENFORCE_QOS_CLASS, QOS_CLASS_USER_INTERACTIVE, -1, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
});
dispatch_block_t block3 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
});
dispatch_async(queue1, block1);
dispatch_async(queue2, block2);
dispatch_async(queue2, block3);

  从运行结果来看,我们使用了DISPATCH_BLOCK_INHERIT_QOS_CLASS标记位,使得block的优先级高于队列的优先级,所以block2始终优先执行。

1
2
3
2018-01-06 21:00:46.583204+0800 HotPatch[4917:43250223] 第二只熊猫宝宝向你奔来...
2018-01-06 21:00:46.583230+0800 HotPatch[4917:43250226] 第一只熊猫宝宝向你奔来...
2018-01-06 21:00:46.583239+0800 HotPatch[4917:43250225] 第三只熊猫宝宝向你奔来...

2.5.2 dispatch_block_perform

  直接执行block,使用这个函数,对block设置优先级是无效的。当然也直接比如block()这样执行block,效果是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
    dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
});
dispatch_block_t block2 = dispatch_block_create_with_qos_class(DISPATCH_BLOCK_DETACHED, QOS_CLASS_USER_INTERACTIVE, -1, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
});

dispatch_block_perform(DISPATCH_BLOCK_DETACHED, block1);
block2();

// 运行结果
2018-01-06 21:15:24.000871+0800 HotPatch[5329:43266428] 第一只熊猫宝宝向你奔来...
2018-01-06 21:15:24.001041+0800 HotPatch[5329:43266428] 第二只熊猫宝宝向你奔来...

2.5.2 dispatch_block_wait

  这个函数的作用是等待block任务完成,再继续往下执行代码。需要注意的是,这个函数的使用是要在立即执行命令之后,或者加入到队列之后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", attr_t);
dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test2", attr_t);
dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
});
dispatch_block_t block2 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝已经抱住你大腿...");
});

dispatch_block_t block3 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
});

dispatch_async(queue1, block1);
dispatch_async(queue2, block2);

dispatch_block_wait(block2, DISPATCH_TIME_FOREVER);

dispatch_async(queue2, block3);
NSLog(@"熊猫妈妈在找熊猫宝宝...");

  看到运行结果,直到block2的任务完成,下面的命令才会被继续执行。

1
2
3
4
5
2018-01-07 09:30:37.900512+0800 HotPatch[7042:43373020] 第二只熊猫宝宝向你奔来...
2018-01-07 09:30:37.900488+0800 HotPatch[7042:43373010] 第一只熊猫宝宝向你奔来...
2018-01-07 09:30:42.971854+0800 HotPatch[7042:43373020] 第二只熊猫宝宝已经抱住你大腿...
2018-01-07 09:30:42.972097+0800 HotPatch[7042:43372948] 熊猫妈妈在找熊猫宝宝...
2018-01-07 09:30:42.972101+0800 HotPatch[7042:43373010] 第三只熊猫宝宝向你奔来...

2.5.3 dispatch_block_notify

  这个函数起到通知的作用,也就是当这个函数监听的任务完成后,会执行dispatch_block_notify函数自己的任务。

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
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", attr_t);
dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test2", attr_t);
dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
});
dispatch_block_t block2 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝已经抱住你大腿...");
});

dispatch_block_t block3 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
});

dispatch_async(queue1, block1);
dispatch_async(queue2, block2);

dispatch_block_notify(block2, queue2, ^{
NSLog(@"捕获一个熊猫宝宝...");
});

dispatch_async(queue2, block3);
NSLog(@"熊猫妈妈在找熊猫宝宝...");

  我们可以看到,dispatch_block_notify的block任务会在block2任务完成后被调用。

1
2
3
4
5
6
2018-01-07 09:35:17.368299+0800 HotPatch[7252:43379370] 熊猫妈妈在找熊猫宝宝...
2018-01-07 09:35:17.368419+0800 HotPatch[7252:43379414] 第一只熊猫宝宝向你奔来...
2018-01-07 09:35:17.368538+0800 HotPatch[7252:43379416] 第二只熊猫宝宝向你奔来...
2018-01-07 09:35:17.368558+0800 HotPatch[7252:43379423] 第三只熊猫宝宝向你奔来...
2018-01-07 09:35:22.369035+0800 HotPatch[7252:43379416] 第二只熊猫宝宝已经抱住你大腿...
2018-01-07 09:35:22.369436+0800 HotPatch[7252:43379416] 捕获一个熊猫宝宝...

  那么,dispatch_block_notify函数的block任务是不是一定是在监听的任务完成后马上调用呢,事实是并不一定的,如果dispatch_block_notify函数的的block任务所在线程前面还有任务没有被执行完毕,那么这个block任务需要等待前面任务的完成,也就没有及时性了。我们举个例子,在串行队列中,就会很明显感到这个:

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
42
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", attr_t);
dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test2", attr_t);
dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
});
dispatch_block_t block2 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第二只熊猫宝宝已经抱住你大腿...");
});

dispatch_block_t block3 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:8];
NSLog(@"第三只熊猫宝宝已经抱住你大腿...");
});

dispatch_block_t block4 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第四只熊猫宝宝向你奔来...");
});

dispatch_sync(queue1, block1);
dispatch_sync(queue2, block2);
dispatch_sync(queue2, block3);

dispatch_block_notify(block2, queue2, ^{
NSLog(@"捕获一个熊猫宝宝...");
});

dispatch_sync(queue2, block4);
NSLog(@"熊猫妈妈在找熊猫宝宝...");

// 运行结果:
2018-01-07 09:45:29.295842+0800 HotPatch[7413:43393215] 第一只熊猫宝宝向你奔来...
2018-01-07 09:45:29.295967+0800 HotPatch[7413:43393215] 第二只熊猫宝宝向你奔来...
2018-01-07 09:45:32.297043+0800 HotPatch[7413:43393215] 第二只熊猫宝宝已经抱住你大腿...
2018-01-07 09:45:32.297180+0800 HotPatch[7413:43393215] 第三只熊猫宝宝向你奔来...
2018-01-07 09:45:40.298124+0800 HotPatch[7413:43393215] 第三只熊猫宝宝已经抱住你大腿...
2018-01-07 09:45:40.298385+0800 HotPatch[7413:43393215] 第四只熊猫宝宝向你奔来...
2018-01-07 09:45:40.298400+0800 HotPatch[7413:43393272] 捕获一个熊猫宝宝...
2018-01-07 09:45:40.298521+0800 HotPatch[7413:43393215] 熊猫妈妈在找熊猫宝宝...

  如果一个block任务被多个监听,那么这几个dispatch_block_notify函数的回调并不是有序的,也就是说并不是写在前面,这个回调最先执行,而是看所在队列,如果谁先被轮到执行就是谁优先。

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
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", attr_t);
dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test2", attr_t);
dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
});
dispatch_block_t block2 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第二只熊猫宝宝已经抱住你大腿...");
});

dispatch_block_t block3 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
});

dispatch_sync(queue1, block1);
dispatch_sync(queue2, block2);

dispatch_block_notify(block2, queue2, ^{
NSLog(@"捕获一个熊猫宝宝1...");
});

dispatch_block_notify(block2, queue1, ^{
NSLog(@"捕获一个熊猫宝宝2...");
});

dispatch_sync(queue2, block3);
NSLog(@"熊猫妈妈在找熊猫宝宝...");
// 运行结果:
2018-01-07 10:01:01.608315+0800 HotPatch[7755:43410127] 第一只熊猫宝宝向你奔来...
2018-01-07 10:01:01.608485+0800 HotPatch[7755:43410127] 第二只熊猫宝宝向你奔来...
2018-01-07 10:01:04.608668+0800 HotPatch[7755:43410127] 第二只熊猫宝宝已经抱住你大腿...
2018-01-07 10:01:04.608838+0800 HotPatch[7755:43410127] 第三只熊猫宝宝向你奔来...
2018-01-07 10:01:04.608862+0800 HotPatch[7755:43410172] 捕获一个熊猫宝宝2...
2018-01-07 10:01:04.608866+0800 HotPatch[7755:43410182] 捕获一个熊猫宝宝1...
2018-01-07 10:01:04.608929+0800 HotPatch[7755:43410127] 熊猫妈妈在找熊猫宝宝...

2.5.4 dispatch_block_cancel

  这个函数的作用就是取消block任务,但是前提就是这个block还没有被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue1 = dispatch_queue_create("com.zhaomu.test1", attr_t);
dispatch_queue_t queue2 = dispatch_queue_create("com.zhaomu.test2", attr_t);
dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
});
dispatch_block_t block2 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第二只熊猫宝宝已经抱住你大腿...");
});

dispatch_block_t block3 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
});

dispatch_async(queue1, block1);
dispatch_async(queue2, block2);
dispatch_async(queue2, block3);

dispatch_block_cancel(block3);

  运行结果可以看到,block3是没有被执行的,已经被取消了,

1
2
3
2018-01-07 18:46:13.991672+0800 HotPatch[8198:43441461] 第一只熊猫宝宝向你奔来...
2018-01-07 18:46:13.991672+0800 HotPatch[8198:43441459] 第二只熊猫宝宝向你奔来...
2018-01-07 18:46:17.063260+0800 HotPatch[8198:43441459] 第二只熊猫宝宝已经抱住你大腿...

  需要注意的是内存问题,如果这个Block是做内存释放的操作,如果block被取消了,会造成内存问题。

2.5.6 dispatch_block_suspend、dispatch_block_resume

  只适用于自定义的队列,不适用于dispatch_get_global_queue等。

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
    dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_CONCURRENT);

dispatch_block_t block1 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第一只熊猫宝宝抱住你的大腿...");
});

dispatch_block_t block2 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝抱住你的大腿...");
});

dispatch_block_t block3 = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第三只熊猫宝宝抱住你的大腿...");
});

dispatch_suspend(queue);

dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_async(queue, block3);

NSLog(@"熊猫妈妈正在找熊猫宝宝....");
[NSThread sleepForTimeInterval:3];
dispatch_resume(queue);

// 运行结果
2018-01-16 11:26:35.628261+0800 HotPatch[95183:56902962] 熊猫妈妈正在找熊猫宝宝....
2018-01-16 11:26:38.629397+0800 HotPatch[95183:56903020] 第一只熊猫宝宝向你奔来...
2018-01-16 11:26:38.629410+0800 HotPatch[95183:56903012] 第二只熊猫宝宝向你奔来...
2018-01-16 11:26:38.629412+0800 HotPatch[95183:56903019] 第三只熊猫宝宝向你奔来...
2018-01-16 11:26:43.629721+0800 HotPatch[95183:56903020] 第一只熊猫宝宝抱住你的大腿...
2018-01-16 11:26:43.629721+0800 HotPatch[95183:56903019] 第三只熊猫宝宝抱住你的大腿...
2018-01-16 11:26:43.629721+0800 HotPatch[95183:56903012] 第二只熊猫宝宝抱住你的大腿...

2.5.7 dispatch_apply

  有时候我们会这样的需求,同时开启多个线程执行某个人物,比如解析多张图片等。这时候我们就没必要写出下面这样的代码

1
2
3
4
5
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(queue, ^{
// TODO SOMETHING
});
}

  更高效的方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);


dispatch_apply(10, queue, ^(size_t idx) {
NSLog(@"%@ --> 第%@只熊猫宝宝向你奔来...", [NSThread currentThread],@(idx));
});

// 运行结果:
2018-01-09 17:46:38.843560+0800 HotPatch[97146:83887263] <NSThread: 0x60000007d100>{number = 1, name = main} --> 第5只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843578+0800 HotPatch[97146:83887379] <NSThread: 0x60c000270b00>{number = 4, name = (null)} --> 第2只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843560+0800 HotPatch[97146:83887378] <NSThread: 0x60000027be80>{number = 7, name = (null)} --> 第4只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843560+0800 HotPatch[97146:83887376] <NSThread: 0x60800026e080>{number = 3, name = (null)} --> 第0只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843593+0800 HotPatch[97146:83887398] <NSThread: 0x60400007f580>{number = 9, name = (null)} --> 第7只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843560+0800 HotPatch[97146:83887377] <NSThread: 0x60c000270c80>{number = 6, name = (null)} --> 第3只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843560+0800 HotPatch[97146:83887397] <NSThread: 0x60800026e0c0>{number = 8, name = (null)} --> 第6只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843603+0800 HotPatch[97146:83887386] <NSThread: 0x60400007f4c0>{number = 5, name = (null)} --> 第1只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843771+0800 HotPatch[97146:83887263] <NSThread: 0x60000007d100>{number = 1, name = main} --> 第9只熊猫宝宝向你奔来...
2018-01-09 17:46:38.843786+0800 HotPatch[97146:83887379] <NSThread: 0x60c000270b00>{number = 4, name = (null)} --> 第8只熊猫宝宝向你奔来...

2.6 dispatch_group

  在实际开发中,我们经常会遇到一种情况,就是需要调用多个接口,然后等接口都请求完毕后,我们再去处理一些事情,比如UI更新等。那么这种情况,可以有多种实现方法,其中之一就是这里要介绍的 – dispatch_group。

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
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
NSLog(@"第一只熊猫向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第一只熊猫抱住你的大腿...");
});

dispatch_group_async(group, queue, ^{
NSLog(@"第二只熊猫向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第二只熊猫抱住你的大腿...");
});

dispatch_group_notify(group, queue, ^{
NSLog(@"捕获所有熊猫宝宝...");
});
// 运行结果:
2018-01-08 08:55:22.261748+0800 HotPatch[12918:43636778] 第一只熊猫向你奔来...
2018-01-08 08:55:22.261746+0800 HotPatch[12918:43636779] 第二只熊猫向你奔来...
2018-01-08 08:55:25.334679+0800 HotPatch[12918:43636779] 第二只熊猫抱住你的大腿...
2018-01-08 08:55:25.334679+0800 HotPatch[12918:43636778] 第一只熊猫抱住你的大腿...
2018-01-08 08:55:25.334900+0800 HotPatch[12918:43636779] 捕获所有熊猫宝宝...

  dispatch_group_notify并不是阻塞线程的,如果想要任务没有完成不必继续往下执行,我们可以使用到dispatch_group_wait函数,起到阻塞线程的作用,当然建议是如果情非得已,不是很推荐使用这样的方式。

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
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
NSLog(@"第一只熊猫向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第一只熊猫抱住你的大腿...");
});

dispatch_group_async(group, queue, ^{
NSLog(@"第二只熊猫向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第二只熊猫抱住你的大腿...");
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"捕获所有熊猫宝宝...");
// 运行结果:
2018-01-08 09:18:28.990738+0800 HotPatch[13852:43662410] 第一只熊猫向你奔来...
2018-01-08 09:18:28.990723+0800 HotPatch[13852:43662408] 第二只熊猫向你奔来...
2018-01-08 09:18:32.062137+0800 HotPatch[13852:43662410] 第一只熊猫抱住你的大腿...
2018-01-08 09:18:32.062137+0800 HotPatch[13852:43662408] 第二只熊猫抱住你的大腿...
2018-01-08 09:18:32.062398+0800 HotPatch[13852:43662317] 捕获所有熊猫宝宝...

  想必,可能很多人有这样的疑问,项目中,大多数后台接口的调用的方式是这样的:

1
2
3
4
5
 [[RequestManager shareInstance] getxxxxSuccess:^{

} failure:^{

}];

  那么,如何把这样的任务,也加入到dispatch_group_t中呢?这时候需要用到这么一对函数:dispatch_group_enter和dispatch_group_leave。

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);
dispatch_group_t group = dispatch_group_create();

[[RequestManager shareInstance] getxxxxSuccess:^{
dispatch_group_enter(group);
} failure:^{
dispatch_group_leave(group);
}];

dispatch_group_async(group, queue, ^{
NSLog(@"接口请求完毕...");
});

  当然,除了dispatch_group可以达到我们的需求,那有没有其他方法也可以实现这个功能呢?

2.7 dispatch_semaphore

  dispatch_semaphore的作用除了可以加锁还可以保持线程同步。首先了解两个函数,dispatch_semaphore_signal表示信号量+1,dispatch_semaphore_wait表示如果信号量为0则进行等待,如果不为0,则继续往下执行并-1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");

[NSThread sleepForTimeInterval:3];

NSLog(@"第一只熊猫宝宝抱住你大腿...");
dispatch_semaphore_signal(semaphore);
});

dispatch_async(queue, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");

[NSThread sleepForTimeInterval:3];

NSLog(@"第二只熊猫宝宝抱住你大腿...");
dispatch_semaphore_signal(semaphore);
});

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"熊猫妈妈在找熊猫宝宝...");

  上面的代码展示了线程同步的问题,当所有任务完成后才继续执行下面的指令。

1
2
3
4
5
2018-01-09 09:05:18.659970+0800 HotPatch[2324:47129676] 第一只熊猫宝宝向你奔来...
2018-01-09 09:05:18.659970+0800 HotPatch[2324:47129675] 第二只熊猫宝宝向你奔来...
2018-01-09 09:05:21.660470+0800 HotPatch[2324:47129676] 第一只熊猫宝宝抱住你大腿...
2018-01-09 09:05:21.660708+0800 HotPatch[2324:47129675] 第二只熊猫宝宝抱住你大腿...
2018-01-09 09:05:21.660749+0800 HotPatch[2324:47129513] 熊猫妈妈在找熊猫宝宝...

  但是一般的建议,除非万不得已,最好不要在主线程调用dispatch_semaphore_wait,毕竟阻塞主线程的事能不做还是不要做的。可以放到异步线程里进行等待:

1
2
3
4
5
dispatch_async(queue, ^{
NSLog(@"你等在原地等待被抱大腿...");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"你的大腿已经被熊猫宝宝抱住...");
});

  当然,也可以控制任务一个接着一个执行。

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
42
43
44
45
46
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");

[NSThread sleepForTimeInterval:3];

NSLog(@"第一只熊猫宝宝抱住你大腿...");
dispatch_semaphore_signal(semaphore);
});

dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"第二只熊猫宝宝向你奔来...");

[NSThread sleepForTimeInterval:3];

NSLog(@"第二只熊猫宝宝抱住你大腿...");
dispatch_semaphore_signal(semaphore);
});

dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"第三只熊猫宝宝向你奔来...");

[NSThread sleepForTimeInterval:3];

NSLog(@"第三只熊猫宝宝抱住你大腿...");
dispatch_semaphore_signal(semaphore);
});

dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"熊猫妈妈在找熊猫宝宝...");
});

// 运行结果:
2018-01-09 17:33:13.467029+0800 HotPatch[96939:83871112] 第一只熊猫宝宝向你奔来...
2018-01-09 17:33:16.467211+0800 HotPatch[96939:83871112] 第一只熊猫宝宝抱住你大腿...
2018-01-09 17:33:16.467420+0800 HotPatch[96939:83871109] 第三只熊猫宝宝向你奔来...
2018-01-09 17:33:19.467450+0800 HotPatch[96939:83871109] 第三只熊猫宝宝抱住你大腿...
2018-01-09 17:33:19.467641+0800 HotPatch[96939:83871110] 第二只熊猫宝宝向你奔来...
2018-01-09 17:33:22.467738+0800 HotPatch[96939:83871110] 第二只熊猫宝宝抱住你大腿...
2018-01-09 17:33:22.467997+0800 HotPatch[96939:83871111] 熊猫妈妈在找熊猫宝宝...

  dispatch_semaphore_wait跟dispatch_semaphore_signal必须匹配的。如果写出dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);这样的代码,而没有dispatch_semaphore_signal与之匹配,就会陷入无限等待。比如在同一个一个线程中,先调用了dispatch_semaphore_wait函数,如下示例:

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
// 错误
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"第一只熊猫宝宝向你奔来...");

[NSThread sleepForTimeInterval:3];

NSLog(@"第一只熊猫宝宝抱住你大腿...");
dispatch_semaphore_signal(semaphore);

// 正确
dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");

[NSThread sleepForTimeInterval:3];

NSLog(@"第一只熊猫宝宝抱住你大腿...");
dispatch_semaphore_signal(semaphore);
});

2.8 dispatch_barrier

2.8.1 dispatch_barrier_async

  dispatch_barrier_async会等待前面的任务执行完毕后,阻塞后面的任务,当自己的任务执行完毕后才会继续执行后面的block任务。需要注意的是,使用这个函数的前提是你使用了自己创建的队列,如果使用dispatch_get_global_queue或者自定义的队列是串行队列则等同于dispatch_async。

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
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);

dispatch_async(queue, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第一只熊猫宝宝抱住了你的大腿...");
});

dispatch_async(queue, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
});

dispatch_barrier_async(queue, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第三只熊猫宝宝抱住了你的大腿...");
});

dispatch_async(queue, ^{
NSLog(@"第四只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第四只熊猫宝宝抱住了你的大腿...");
});

// 运行结果
2018-01-16 09:08:18.239814+0800 HotPatch[91915:56702026] 第二只熊猫宝宝向你奔来...
2018-01-16 09:08:18.239828+0800 HotPatch[91915:56702034] 第一只熊猫宝宝向你奔来...
2018-01-16 09:08:23.306770+0800 HotPatch[91915:56702026] 第二只熊猫宝宝抱住了你的大腿...
2018-01-16 09:08:23.306783+0800 HotPatch[91915:56702034] 第一只熊猫宝宝抱住了你的大腿...
2018-01-16 09:08:23.307009+0800 HotPatch[91915:56702034] 第三只熊猫宝宝向你奔来...
2018-01-16 09:08:28.377030+0800 HotPatch[91915:56702034] 第三只熊猫宝宝抱住了你的大腿...
2018-01-16 09:08:28.377252+0800 HotPatch[91915:56702034] 第四只熊猫宝宝向你奔来...
2018-01-16 09:08:33.451332+0800 HotPatch[91915:56702034] 第四只熊猫宝宝抱住了你的大腿...

  看下,如果使用的不是自定义队列的效果:

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
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//dispatch_queue_create("com.zhaomu.test", attr_t);

dispatch_async(queue, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第一只熊猫宝宝抱住了你的大腿...");
});

dispatch_async(queue, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
});

dispatch_barrier_async(queue, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第三只熊猫宝宝抱住了你的大腿...");
});

dispatch_async(queue, ^{
NSLog(@"第四只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第四只熊猫宝宝抱住了你的大腿...");
});
NSLog(@"熊猫妈妈找熊猫宝宝....");

// 运行结果
2018-01-16 09:14:19.930798+0800 HotPatch[92146:56719799] 第一只熊猫宝宝向你奔来...
2018-01-16 09:14:19.930798+0800 HotPatch[92146:56719798] 第二只熊猫宝宝向你奔来...
2018-01-16 09:14:19.930798+0800 HotPatch[92146:56719706] 熊猫妈妈找熊猫宝宝....
2018-01-16 09:14:19.930807+0800 HotPatch[92146:56719797] 第三只熊猫宝宝向你奔来...
2018-01-16 09:14:19.930819+0800 HotPatch[92146:56719811] 第四只熊猫宝宝向你奔来...
2018-01-16 09:14:24.930909+0800 HotPatch[92146:56719799] 第一只熊猫宝宝抱住了你的大腿...
2018-01-16 09:14:24.930910+0800 HotPatch[92146:56719811] 第四只熊猫宝宝抱住了你的大腿...
2018-01-16 09:14:24.930910+0800 HotPatch[92146:56719797] 第三只熊猫宝宝抱住了你的大腿...
2018-01-16 09:14:24.930909+0800 HotPatch[92146:56719798] 第二只熊猫宝宝抱住了你的大腿...

2.8.2 dispatch_barrier_sync

  这个函数可以起到阻塞线程的作用,同样的,前提是必须使用自定义的并行队列,串行队列也不可以,不然等同于dispatch_sync。

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
    dispatch_queue_attr_t attr_t = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,  QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY);
dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", attr_t);

dispatch_async(queue, ^{
NSLog(@"第一只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第一只熊猫宝宝抱住了你的大腿...");
});

dispatch_async(queue, ^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
});

dispatch_barrier_sync(queue, ^{
NSLog(@"第三只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第三只熊猫宝宝抱住了你的大腿...");
});

dispatch_async(queue, ^{
NSLog(@"第四只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第四只熊猫宝宝抱住了你的大腿...");
});
NSLog(@"熊猫妈妈找熊猫宝宝....");

// 运行结果,可以看到主线程也被阻塞了
2018-01-16 09:16:55.716809+0800 HotPatch[92335:56726566] 第一只熊猫宝宝向你奔来...
2018-01-16 09:16:55.716792+0800 HotPatch[92335:56726568] 第二只熊猫宝宝向你奔来...
2018-01-16 09:17:00.784030+0800 HotPatch[92335:56726568] 第二只熊猫宝宝抱住了你的大腿...
2018-01-16 09:17:00.784030+0800 HotPatch[92335:56726566] 第一只熊猫宝宝抱住了你的大腿...
2018-01-16 09:17:00.784281+0800 HotPatch[92335:56726517] 第三只熊猫宝宝向你奔来...
2018-01-16 09:17:05.785264+0800 HotPatch[92335:56726517] 第三只熊猫宝宝抱住了你的大腿...
2018-01-16 09:17:05.785450+0800 HotPatch[92335:56726517] 熊猫妈妈找熊猫宝宝....
2018-01-16 09:17:05.785495+0800 HotPatch[92335:56726569] 第四只熊猫宝宝向你奔来...
2018-01-16 09:17:10.850254+0800 HotPatch[92335:56726569] 第四只熊猫宝宝抱住了你的大腿...

2.9 dispatch_source

  通过dispatch_source_create创建一个对象。

1
dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue);

  第一个参数是一个dispatch_source类型,提供的类型如下:

1
2
3
4
5
6
7
8
9
10
11
DISPATCH_SOURCE_TYPE_DATA_ADD
DISPATCH_SOURCE_TYPE_DATA_OR
DISPATCH_SOURCE_TYPE_MACH_RECV
DISPATCH_SOURCE_TYPE_MACH_SEND
DISPATCH_SOURCE_TYPE_PROC
DISPATCH_SOURCE_TYPE_READ
DISPATCH_SOURCE_TYPE_SIGNAL
DISPATCH_SOURCE_TYPE_TIMER
DISPATCH_SOURCE_TYPE_VNODE
DISPATCH_SOURCE_TYPE_WRITE
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE

  例子之前讲过一个定时器,在dispatch_source中,可能算是比较常用的一种。其他具体使用场景,暂未接触到,如果有更好的场景,后面再加进来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)viewDidLoad {
[super viewDidLoad];

dispatch_queue_t queue = dispatch_queue_create("com.zhaomu.test", DISPATCH_QUEUE_CONCURRENT);

self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.source, dispatch_walltime(NULL, 0), 5ull * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.source, ^{
[self testSelector];
});
dispatch_source_set_cancel_handler(self.source, ^{
NSLog(@"");
});
dispatch_resume(self.source);
}

- (void)testSelector {
NSLog(@"一只熊猫宝宝向您奔来....");
}


3. NSOperation

  创建NSOpertioan对象,一般通过NSInvocationOperation和NSBlockOperation来创建。

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
- (void)viewDidLoad {
[super viewDidLoad];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"com.zhaomu.test";
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test1) object:nil];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
}];

// 加入到队列自动执行任务
[queue addOperation:op1];
[queue addOperation:op2];

// 手动执行任务
// [op1 start];
// [op2 start];
}

- (void)test1 {
NSLog(@"第一只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第一只熊猫宝宝抱住了你的大腿...");
}

// 运行结果
2018-01-16 10:27:55.292137+0800 HotPatch[93798:56833792] 第一只熊猫宝宝向你奔来...
2018-01-16 10:27:55.292138+0800 HotPatch[93798:56833794] 第二只熊猫宝宝向你奔来...
2018-01-16 10:28:00.297201+0800 HotPatch[93798:56833792] 第一只熊猫宝宝抱住了你的大腿...
2018-01-16 10:28:00.297202+0800 HotPatch[93798:56833794] 第二只熊猫宝宝抱住了你的大腿...

  需要特别说一下的是一个NSBlockOperation对象可以不断添加任务,其任务之间是并行执行的。

1
2
3
4
5
6
7
8
9
10
11
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"第二只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
}];

[op2 addExecutionBlock:^{
NSLog(@"第四只熊猫宝宝向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第四只熊猫宝宝抱住了你的大腿...");
}];

  当然也是可以设置任务优先级。可以通过queuePriority和qualityOfService设置任务优先级。而且队列之间也可以qualityOfService设置队列的优先级。
  那么相比于GCD,NSOperation有哪些优点呢?

  • 设置最大并发数: maxConcurrentOperationCount

    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
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"com.zhaomu.test";
    queue.maxConcurrentOperationCount = 2;

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第一只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第一只熊猫宝宝抱住了你的大腿...");
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第二只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
    }];

    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第三只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第三只熊猫宝宝抱住了你的大腿...");
    }];

    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

    // 运行结果
    2018-01-16 10:31:10.979448+0800 HotPatch[93888:56839976] 第一只熊猫宝宝向你奔来...
    2018-01-16 10:31:10.979449+0800 HotPatch[93888:56839975] 第二只熊猫宝宝向你奔来...
    2018-01-16 10:31:15.981534+0800 HotPatch[93888:56839976] 第一只熊猫宝宝抱住了你的大腿...
    2018-01-16 10:31:15.981534+0800 HotPatch[93888:56839975] 第二只熊猫宝宝抱住了你的大腿...
    2018-01-16 10:31:15.981721+0800 HotPatch[93888:56839983] 第三只熊猫宝宝向你奔来...
    2018-01-16 10:31:20.985040+0800 HotPatch[93888:56839983] 第三只熊猫宝宝抱住了你的大腿...
  • 更方便的设置依赖: addDependency

    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
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"com.zhaomu.test";

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第一只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第一只熊猫宝宝抱住了你的大腿...");
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第二只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
    }];

    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第三只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第三只熊猫宝宝抱住了你的大腿...");
    }];

    [op2 addDependency:op1];
    [op3 addDependency:op2];

    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

    // 运行结果
    2018-01-16 10:33:33.063585+0800 HotPatch[93949:56842708] 第一只熊猫宝宝向你奔来...
    2018-01-16 10:33:38.066227+0800 HotPatch[93949:56842708] 第一只熊猫宝宝抱住了你的大腿...
    2018-01-16 10:33:38.066432+0800 HotPatch[93949:56842705] 第二只熊猫宝宝向你奔来...
    2018-01-16 10:33:43.066665+0800 HotPatch[93949:56842705] 第二只熊猫宝宝抱住了你的大腿...
    2018-01-16 10:33:43.066902+0800 HotPatch[93949:56842705] 第三只熊猫宝宝向你奔来...
    2018-01-16 10:33:48.071874+0800 HotPatch[93949:56842705] 第三只熊猫宝宝抱住了你的大腿...
  • 直接取消队列内的所有任务: cancelAllOperations

  • 阻塞线程等待队列任务全部完成:waitUntilAllOperationsAreFinished
  • 更直观的看到当前队列有多少任务: operationCount
    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
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"com.zhaomu.test";

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第一只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第一只熊猫宝宝抱住了你的大腿...");
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第二只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第二只熊猫宝宝抱住了你的大腿...");
    }];

    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"第三只熊猫宝宝向你奔来...");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"第三只熊猫宝宝抱住了你的大腿...");
    }];

    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

    [queue waitUntilAllOperationsAreFinished];
    NSLog(@"熊猫妈妈正在找熊猫宝宝...");

    // 运行结果:
    2018-01-16 10:46:02.731265+0800 HotPatch[94363:56860859] 第三只熊猫宝宝向你奔来...
    2018-01-16 10:46:02.731265+0800 HotPatch[94363:56860858] 第二只熊猫宝宝向你奔来...
    2018-01-16 10:46:02.731265+0800 HotPatch[94363:56860865] 第一只熊猫宝宝向你奔来...
    2018-01-16 10:46:07.735805+0800 HotPatch[94363:56860859] 第三只熊猫宝宝抱住了你的大腿...
    2018-01-16 10:46:07.735805+0800 HotPatch[94363:56860865] 第一只熊猫宝宝抱住了你的大腿...
    2018-01-16 10:46:07.735813+0800 HotPatch[94363:56860858] 第二只熊猫宝宝抱住了你的大腿...
    2018-01-16 10:46:07.736033+0800 HotPatch[94363:56860808] 熊猫妈妈正在找熊猫宝宝...

4. pthread

  pthread更接近底层,这里只做简单的了解即可。代码如下:

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
- (void)viewDidLoad {
[super viewDidLoad];

pthread_t thread1;
pthread_t thread2;
pthread_create(&thread1, NULL, test, (__bridge void *)@"熊猫宝宝跑出来拉");
// 阻塞,等待thread1任务完成
pthread_join(thread1, NULL);
pthread_create(&thread2, NULL, test2, NULL);
// 线程结束后自动释放资源
pthread_detach(thread1);
pthread_detach(thread2);
}

void *test(void *data) {
NSLog(@"%@", (__bridge NSString *)data);
NSLog(@"第一只小熊猫向你奔来...");
[NSThread sleepForTimeInterval:5];
NSLog(@"第一只小熊猫抱住你大腿...");
return NULL;
}

void *test2(void *data) {
NSLog(@"第二只小熊猫向你奔来...");
[NSThread sleepForTimeInterval:3];
NSLog(@"第二只小熊猫抱住你大腿...");
return NULL;
}

// 运行结果:
2018-02-07 17:31:20.203050+0800 HotPatch[84419:80030841] 熊猫宝宝跑出来拉
2018-02-07 17:31:20.203215+0800 HotPatch[84419:80030841] 第一只小熊猫向你奔来...
2018-02-07 17:31:25.206790+0800 HotPatch[84419:80030841] 第一只小熊猫抱住你大腿...
2018-02-07 17:31:25.207014+0800 HotPatch[84419:80030951] 第二只小熊猫向你奔来...
2018-02-07 17:31:28.211189+0800 HotPatch[84419:80030951] 第二只小熊猫抱住你大腿...

  如何使用多线程,至此结束,下一篇将要看一看GCD的源码。