GCD Queue中其他的API

dispatch_apply

按指定的次数将指定的任务追加到指定的队列中,实际上就是多次执行任务。并等待全部任务执行结束(同dispatch_sync一样会等待),推荐在dispatch_async使用dispatch_apple。

1
2
3
dispatch_apply(size_t iterations, 
dispatch_queue_t queue,
void (^block)(size_t));

参数说明

参数 类型 描述
iterations size_t 任务执行的次数
queue dispatch_queue_t 队列
(^block)(size_t) void 有参数的代码块 执行的任务

简单使用

1
2
3
4
5
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"执行任务 %@。", @(index));
});
NSLog(@"完成.");

因为apply是同步函数,会等待,所以完成会最后执行。

替代for循环

在需要对NSArray类对象中的所有元素处理时,可能需要调用一些函数进行计算,就不必一个一个编写for循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

NSArray *users = @[@"user1", @"user2", @"user3"];

dispatch_async(queue, ^{

// 等待dispatch_apply函数全部之行结束。
dispatch_apply(users.count, queue, ^(size_t index) {
// 并列处理
NSLog(@"执行任务 %@", users[index]);
});

dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"完成. 刷新界面。");
});
});

dispatch_set_target_queue

自定义队列,都会使用与默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT相同的执行任务的优先级线程。而需要手动变更生成的执行优先级就需要使用dispatch_set_target_queue

  • 第一个参数指定需要改变执行优先级的队列。
  • 第二个参数是第一个参数的队列改变后的优先级。
  • 第一个参数不能指定全局队列和主队列。

默认优先级修改成后台优先级

1
2
3
4
dispatch_queue_t serialQueue = dispatch_queue_create("com.lucaslz.db", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_set_target_queue(serialQueue, backgroundQueue);

并发执行修改成串行执行

如果多个串行队列中使用dispatch_set_target_queue函数指定目标为同一个串行队列,那么本应该并行执行的多个串行队列,在目标串行队列上只能同时执行一个处理。这可以在必须不可并发执行的任务追加到多个串行队列中时,可以使用dispatch_set_target_queue,防止并行执行。

dispatch_after

注意:dispatch_after是延时添加任务到队列中,而不是延时立即执行。

延迟三秒后添加任务到主队列。

1
2
3
4
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3ull * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"等待...");
});

这个延迟时间不是很准确,比如,队列中本身就有大量的任务需要等待执行时,这时候追加一个延迟时间的任务,这个时间会更长。

参数说明

参数 类型 描述
when dispatch_time_t 使用dispatch_time或者dispatch_walltime创建
queue dispatch_queue_t 队列
block dispatch_block_t 代码块 即任务

dispatch_time函数

参数 类型 描述
when dispatch_time_t 开始时间,经常使用DISPATCH_TIME_NOW
delta int64_t 纳秒

ull 是C语言的数值字面量,显示表明类型是unsigned long long
因为这个时间数字太大了,所以系统提供了三个宏来简化。

1
2
3
4
#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
  • NSEC_PER_SEC 每秒有多少纳秒
  • NSEC_PER_MSEC 每毫秒有多少纳秒
  • USEC_PER_SEC 每秒有多少微秒
  • NSEC_PER_USEC 每微秒有多少纳秒

秒、毫秒、微秒、纳秒相互转换

  • 1秒 = 1000 毫秒 = 1000 1000 微秒= 1000 1000 * 1000 纳秒
  • 1毫秒 = 1000 微秒 = 1000 * 1000 纳秒
  • 1微秒 = 1000 纳秒

因此一秒有多种方式方式

  • 1ull * NSEC_PER_SEC 1秒
  • 1000ull * NSEC_PER_MSEC 1秒
  • 1ull USEC_PER_SEC NSEC_PER_USEC 1秒

dispatch_barrier_async

在访问数据库的时候,使用串行队列可以避免数据竞争。

写入任务确实不可以与其他的写入任务以及包含读取任务等并行执行。但是读取任务只是与读取任务并行执行,那么多个并行执行就不会发生问题。

为了提高效率,读取任务追加到并行队列中,写入任务在没有读取任务执行的状态下,追加到串行任务中。(写入任务处理结束,读取任务才能执行)

使用dispatch_barrier_async函数就能够达到此目的,

1
2
3
4
5
6
7
8
9
10
11
dispatch_queue_t queue = dispatch_queue_create("com.lucaslz.db", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
NSLog(@"read 1");
});
dispatch_barrier_async(queue, ^{
NSLog(@"write 1");
});
dispatch_async(queue, ^{
NSLog(@"read 4");
});

这既能保证高效的读取,又能保证写入时不会放生数据竞争。

dispatch_barrier_async会等待追加到并行队列中所有任务全部执行结束后,再将指定的任务追加到并行队列中,当dispatch_barrier_async函数追加的任务处理的结束后,并行队列才恢复成并行执行任务。

不过在FMDB这个框架中FMDatabaseQueue是串行队列。

specific

队列关联对象,把一段时间直接关联到队列上。这个有点像objc_setAssociatedObject函数,在运行时,给对象关联一个其他的对象。

dispatch_get_current_queue()这个函数过时了,iOS6以后都不推荐使用了。使用dispatch_get_current_queue主要是用于判断dispatch_sync是否会死锁。这时候就可以使用dispatch_queue_set_specific设置一个标记。使用dispatch_get_specific来查询这个标记。

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t queue = dispatch_queue_create("com.lucaslz.db", DISPATCH_QUEUE_CONCURRENT);

static const void * const queueKey = &queueKey;

dispatch_queue_set_specific(queue, queueKey, (__bridge void *)queue, NULL);

dispatch_async(queue, ^{
dispatch_queue_t currentQueue = (__bridge dispatch_queue_t)(dispatch_get_specific(queueKey));
if (currentQueue == queue) {
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
static const void * const queueKey = &queueKey;

BOOL dispatch_serial_current_is_tagged_queue(dispatch_queue_t queue) {
dispatch_queue_t currentQueue = (__bridge dispatch_queue_t)(dispatch_get_specific(queueKey));
return currentQueue == queue;
}

void dispatch_serial_sync_safe(dispatch_queue_t queue, dispatch_block_t block) {
if (dispatch_serial_current_is_tagged_queue(queue)) {
block();
} else {
dispatch_sync(queue, block);
}
}

- (void)safeQueue {
dispatch_queue_t queue = dispatch_queue_create("com.lucaslz.db", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue, queueKey, (__bridge void *)queue, NULL);

dispatch_serial_sync_safe(queue, ^{
dispatch_serial_sync_safe(queue, ^{
NSLog(@"正常执行。");
});
});
}

异步

1
2
3
4
5
6
7
8
9
10
11
12
13
void dispatch_serial_async_safe(dispatch_queue_t queue, dispatch_block_t block) {
if (dispatch_serial_current_is_tagged_queue(queue)) {
block();
} else {
dispatch_async(queue, block);
}
}

dispatch_serial_async_safe(queue, ^{
dispatch_serial_sync_safe(queue, ^{
NSLog(@"执行");
});
});

主队列

主队列判断因为NSThread有方法可以判断当前队列是不是主队列。

同步

1
2
3
4
5
6
7
void dispatch_main_sync_safe(dispatch_block_t block) {
if ([NSThread isMainThread]) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}

异步

1
2
3
4
5
6
7
void dispatch_main_async_safe(dispatch_block_t block) {
if ([NSThread isMainThread]) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
坚持原创技术分享,您的支持将鼓励我继续创作!