dispatch_after
功能:延迟一段时间把一项任务提交到队列中执行,返回之后就不能取消
常用来在在主队列上延迟执行一项任务
示例代码:
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//立即打印一条信息
NSLog(@"Begin add block...");
//提交一个block
dispatch_async(queue, ^{
//Sleep 10秒
[NSThread sleepForTimeInterval:10];
NSLog(@"First block done...");
});
//5 秒以后提交block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"After...");
});
结果如下:
2015-03-31 20:57:27.122 GCDTest[45633:1812016] Begin add block...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] First block done...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] After...
dispatch_after是延迟提交,并不是延时后立即执行
dispatch_once
功能:保证在APP运行期间,block中的代码只执行一次
不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
示例代码:
// 如果你要共享某个实例
+(MyInstance*)shareMyInstance(){
static MyInstance *inst=nil;
static dispatch_once_t p;
dispatch_once(&p,^{
inst=[[MyInstance alloc]init];
});
return inst;
}
现在在应用中就有一个共享的实例,该实例只会被创建一次。
单例方法有很多优势:
1 线程安全
2 很好满足静态分析器要求
3 和自动引用计数(ARC)兼容
4 仅需要少量代码
dispatch_group
多个并发下载任务,dispatch_group 可以帮我们实现控制在第一时间知道任务全部完成。
dispatch_group_t group = dispatch_group_create();
// 某个任务放进 group
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任务代码1
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任务代码2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 任务全部完成处理
NSLog(@"isover");
});
创建一个任务组,然后将异步操作放进组里面,在最后用notify 告知所有任务完成,并做相应处理,一般来说都是在主线程里面刷新UI来提示用户了。你如果不依赖UI放进子线程里面也是没有问题的。当然group同步的方式还有其他
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i ++)
{
dispatch_group_enter(group);
// 任务代码i 假定任务 是异步执行block回调
// block 回调执行
dispatch_group_leave(group);
// block 回调执行
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程处理
});
首先我们异步执行,因为dispatch_group_wait函数是阻塞的,for里面安排了三个任务,这三个任务都是加载,在任务开始前 调用 enter,任务完成时调用leave,wait函数一直阻塞,直到它发现group里面的任务全部leave,它才放弃阻塞(任务全部完成),然后我们在主线程更新UI告知用户.
dispatch_group_notify函数会隐式retain 当先的调用者,在使用的时候要知道这一点
dispatch_apply
<li>基本用法:</li>
dispatch_apply函数是dispatch_sync函数和dispatch group的关联API,该函数按指定的次数将指定的block追加到指定的dispatch queue中,并等到全部的处理执行结束
/*!
* @brief dispatch_apply的用法
*/
- (void)dispatchApplyTest1 {
//生成全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函数说明
*
* @brief dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
* 该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
*
* @param 10 指定重复次数 指定10次
* @param queue 追加对象的Dispatch Queue
* @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
NSLog(@"done");
/*!
* @brief 输出结果
*
2016-02-25 19:24:39.102 dispatch_apply测试[2985:165004] 0
2016-02-25 19:24:39.102 dispatch_apply测试[2985:165086] 1
2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 4
2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 5
2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 6
2016-02-25 19:24:39.103 dispatch_apply测试[2985:165088] 3
2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 7
2016-02-25 19:24:39.105 dispatch_apply测试[2985:165004] 8
2016-02-25 19:24:39.105 dispatch_apply测试[2985:165004] 9
2016-02-25 19:24:39.102 dispatch_apply测试[2985:165087] 2
2016-02-25 19:24:39.105 dispatch_apply测试[2985:165004] done
* !!!因为在Global Dispatch Queue中执行,所以各个处理的执行时间不定
但done一定会输出在最后的位置,因为dispatch_apply函数会等待所以的处理结束
*/
}
<li>使用技巧:模拟for循环</li>
/*!
* @brief 实例:当要对NSArray类对象的所有元素执行处理时,不必一个一个的编写for循环部分
*/
- (void)dispatchApplyTest2 {
//1.创建NSArray类对象
NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
//2.创建一个全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.通过dispatch_apply函数对NSArray中的全部元素进行处理,并等待处理完成,
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
NSLog(@"done");
/*!
* @brief 输出结果
*
2016-02-25 19:37:17.308 dispatch_apply测试[3010:167871] 0: a
2016-02-25 19:37:17.308 dispatch_apply测试[3010:167956] 1: b
2016-02-25 19:37:17.308 dispatch_apply测试[3010:167957] 3: d
2016-02-25 19:37:17.308 dispatch_apply测试[3010:167871] 4: e
2016-02-25 19:37:17.309 dispatch_apply测试[3010:167957] 6: g
2016-02-25 19:37:17.309 dispatch_apply测试[3010:167871] 7: h
2016-02-25 19:37:17.309 dispatch_apply测试[3010:167957] 8: i
2016-02-25 19:37:17.309 dispatch_apply测试[3010:167871] 9: j
2016-02-25 19:37:17.308 dispatch_apply测试[3010:167956] 5: f
2016-02-25 19:37:17.308 dispatch_apply测试[3010:167955] 2: c
* !!!因为在Global Dispatch Queue中执行,所以各个处理的执行时间不定
但done一定会输出在最后的位置,因为dispatch_apply函数会等待所以的处理结束
*/
}
<li>模拟dispatch_sync的同步效果</li>
在dispatch_async函数中异步执行dispatch_apply函数,模拟dispatch_sync的同步效果
/*!
* @brief 推荐在dispatch_async函数中异步执行dispatch_apply函数
效果 dispatch_apply函数与dispatch_sync函数形同,会等待处理执行结束
*/
- (void)dispatchApplyTest3 {
NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程执行用户界面更新等操作");
});
});
/*!
* @brief 执行结果
*
2016-02-25 19:49:53.189 dispatch_apply测试[3060:171856] 3: d
2016-02-25 19:49:53.189 dispatch_apply测试[3060:171852] 1: b
2016-02-25 19:49:53.189 dispatch_apply测试[3060:171853] 2: c
2016-02-25 19:49:53.189 dispatch_apply测试[3060:171850] 0: a
2016-02-25 19:49:53.189 dispatch_apply测试[3060:171856] 4: e
2016-02-25 19:49:53.189 dispatch_apply测试[3060:171852] 5: f
2016-02-25 19:49:53.190 dispatch_apply测试[3060:171853] 6: g
2016-02-25 19:49:53.190 dispatch_apply测试[3060:171850] 7: h
2016-02-25 19:49:53.190 dispatch_apply测试[3060:171852] 9: j
2016-02-25 19:49:53.190 dispatch_apply测试[3060:171856] 8: i
2016-02-25 19:49:53.218 dispatch_apply测试[3060:171760] 回到主线程执行用户界面更新等操作
*
*/
}
dispatch_semaphore
当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,在GCD中快速的控制并发的方法就是dispatch_semaphore
信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
简单的介绍一下这三个函数,
dispatch_semaphore_create有一个整形的参数,我们可以理解为信号的总量,
dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,
dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_release(semaphore);
简单的介绍一下这一段代码,创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。
简单示例:
__block BOOL isok = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
Engine *engine = [[Engine alloc] init];
[engine queryCompletion:^(BOOL isOpen) {
isok = isOpen;
dispatch_semaphore_signal(sema);
} onError:^(int errorCode, NSString *errorMessage) {
isok = NO;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
dispatch_barrier
在并行队列中,有的时候我们需要让某个任务单独执行,也就是他执行的时候不允许其他任务执行。这时候dispatch_barrier就派上了用场。
使用dispatch_barrier将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完成
dispatch_barrier在GCD中有4个API
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
如果API在串行队列中调用,将等同于dispatch_async、dispatch_async_f、dispatch_sync、dispatch_sync_f,不会有任何影响。
dispatch_barrier最典型的使用场景是读写问题,NSMutableDictionary在多个线程中如果同时写入,或者一个线程写入一个线程读取,会发生无法预料的错误。但是他可以在多个线程中同时读取。如果多个线程同时使用同一个NSMutableDictionary。怎样才能保护NSMutableDictionary不发生意外呢?
- (void)setObject:(id)anObject forKey:(id
)aKey
{
dispatch_barrier_async(self.concurrentQueue, ^{
[self.mutableDictionary setObject:anObject forKey:aKey];
});
}
- (id)objectForKey:(id)aKey
{
__block id object = nil; dispatch_sync(self.concurrentQueue, ^{
object = [self.mutableDictionary objectForKey:aKey];
}); return object;
}
当NSMutableDictionary写入的时候,我们使用dispatch_barrier_async,让其单独执行写入操作,不允许其他写入操作或者读取操作同时执行。当读取的时候,我们只需要直接使用dispatch_sync,让其正常读取即可。这样就可以保证写入时不被打扰,读取时可以多个线程同时进行
dispatch_set(get)_context
先看看这两个函数的原型:
//设置context
void dispatch_set_context ( dispatch_object_t object, void *context );
//获取context
void * dispatch_get_context ( dispatch_object_t object );
这里的object一般指的就是通过dispatch_queue_create创建的队列。
所以,这两个函数分别完成了将context“绑定”到特定GCD队列和从GCD队列获取对应context的任务。
<li>什么是context</li>
在上述函数原型中,context是一个“void类型指针”,学过C语言的朋友应该都知道,void型指针可以指向任意类型,就是说,context在这里可以是任意类型的指针。
从这里可以得知,我们可以为队列“set”任意类型的数据,并在合适的时候取出来用。
<li>用malloc创建context并绑定到队列上</li>
参考Apple官方的例子,我们先用传统的malloc创建context,看看如下简短例子:
//定义context,即一个结构体
typedef struct _Data {
int number;
} Data;
//定义队列的finalizer函数,用于释放context内存
void cleanStaff(void *context) {
NSLog(@"In clean, context number: %d", ((Data *)context)->number);
//释放,如果是new出来的对象,就要用delete
free(context);
}
- (void)testBody {
//创建队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//创建Data类型context数据并初始化
Data *myData = malloc(sizeof(Data));
myData->number = 10;
//绑定context
dispatch_set_context(queue, myData);
//设置finalizer函数,用于在队列执行完成后释放对应context内存
dispatch_set_finalizer_f(queue, cleanStaff);
dispatch_async(queue, ^{
//获取队列的context数据
Data *data = dispatch_get_context(queue);
//打印
NSLog(@"1: context number: %d", data->number);
//修改context保存的数据
data->number = 20;
});
}
运行结果:
2015-03-29 20:28:16.854 GCDTest[37787:1443423] 1: context number: 10
2015-03-29 20:28:16.855 GCDTest[37787:1443423] In clean, context number: 20
通过为队列设置context,我们就能为队列绑定自定义的数据,然后在合适的时候取出来用。
<li>NSObject类型的context</li>
在Mac、iOS的开发过程中,我们大部分用的都是Foundation框架下的类,就是如NSString、NSDictionary这些NSObject类型的类。
但是上面的dispatch_set(get)_context接受的context参数是C语言类型的,即Core Foundation类型的,我们如何转换呢?
由于ARC不能管理Core Foundation Object的生命周期,所以我们必须先转换context的“类型”,以便转换内存管理权。
<li>__bridge</li>
__bridge: 只做了类型转换,不修改内存管理权;
__bridge_retained(即CFBridgingRetain)转换类型,同时将内存管理权从ARC中移除,后面需要使用CFRelease来释放对象;
__bridge_transfer(即CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将内存管理权交给ARC。
<li>重新定义context</li>
为了方便下面的说明,我们先定义context类。
@interface Data : NSObject
@property(assign, nonatomic) int number;
@end
@implementation Data
//继承dealloc方法,便于观察对象何时被释放
- (void)dealloc {
NSLog(@"Data dealloc...");
}
@end
看,我们继承了dealloc方法,这样就能知道Data类型对象什么时候被释放。
注意:
__bridge的转换是没有转移内存管理权的,这点要特别注意。
如果在传context对象时,用的是__bridge转换,那么context对象的内存管理权还在ARC手里,一旦当前作用域执行完,context就会被释放,而如果队列的任务用了context对象,就会造成“EXC_BAD_ACCESS”崩溃!
<li>正确的用法</li>
重写上面的例子,如下:
//定义队列的finalizer函数,用于释放context内存
void cleanStaff(void *context) {
//这里用__bridge转换,不改变内存管理权
Data *data = (__bridge Data *)(context);
NSLog(@"In clean, context number: %d", data.number);
//释放context的内存!
CFRelease(context);
}
- (void)testBody {
//创建队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//创建Data类型context数据并初始化
Data *myData = [Data new];
myData.number = 10;
//绑定context
//这里用__bridge_retained转换,将context的内存管理权从ARC移除,交由我们自己手动释放!
dispatch_set_context(queue, (__bridge_retained void *)(myData));
//设置finalizer函数,用于在队列执行完成后释放对应context内存
dispatch_set_finalizer_f(queue, cleanStaff);
dispatch_async(queue, ^{
//获取队列的context数据
//这里用__bridge转换,不改变内存管理权
Data *data = (__bridge Data *)(dispatch_get_context(queue));
//打印
NSLog(@"1: context number: %d", data.number);
//修改context保存的数据
data.number = 20;
});
}
解释:
>在dispatch_set_context的时候用__bridge_retained转换,将context的内存管理权从ARC移除,交给我们自己管理。
在队列任务中,用dispatch_get_context获取context的时候,用__bridge转换,维持context的内存管理权不变,防止出了作用域context被释放。
最后用CFRelease释放context内存。
运行结果:
2015-03-29 21:12:41.631 GCDTest[38131:1465900] 1: context number: 10
2015-03-29 21:12:41.632 GCDTest[38131:1465900] In clean, context number: 20
2015-03-29 21:12:41.632 GCDTest[38131:1465900] Data dealloc...
由结果可知,我们的context对象在最后显式调用CFRelease才被释放。
总的来说,就是合理运用__bridge_retained(transfer)关键字转换对象的内存管理权,让我们自己控制对象的生命周期。
set_specific & get_specific
有时候我们需要将某些东西关联到队列上,比如我们想在某个队列上存一个东西,或者我们想区分2个队列。GCD提供了dispatch_queue_set_specific方法,通过key,将context关联到queue上
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);
queue:需要关联的queue,不允许传入NULL
key:唯一的关键字
context:要关联的内容,可以为NULL
destructor:释放context的函数,当新的context被设置时,destructor会被调用
有存就有取,将context关联到queue上之后,可以通过dispatch_queue_get_specific或者dispatch_get_specific方法将值取出来。
void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void *dispatch_get_specific(const void *key);
dispatch_queue_get_specific: 根据queue和key取出context,queue参数不能传入全局队列
dispatch_get_specific: 根据唯一的key取出当前queue的context。如果当前queue没有key对应的context,则去queue的target queue取,取不着返回NULL,如果对全局队列取,也会返回NULL
iOS 6之后dispatch_get_current_queue()被废弃(废弃的原因这里不多解释,如果想了解可以看这里),如果我们需要区分不同的queue,可以使用set_specific方法。根据对应的key是否有值来区分
dispatch_time_t
// dispatch_time_t一般在dispatch_after和dispatch_group_wait等方法里作为参数使用。这里最需要注意的是一些宏的含义。
// NSEC_PER_SEC,每秒有多少纳秒。
// USEC_PER_SEC,每秒有多少毫秒。
// NSEC_PER_USEC,每毫秒有多少纳秒。
// DISPATCH_TIME_NOW 从现在开始
// DISPATCH_TIME_FOREVE 永久
// time为1s的写法
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);