GCD的API学习
全称是Grand Central Dispatch,是异步执行任务的技术之一.将任务(所要做的操作)追加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行.GCD中主要有2个用来执行任务(block)的函数:同步和异步.同步就是说在当前的线程当中执行任务.异步在另外一条线程当中执行任务.
- (1)同步(在当前线程中执行任务):dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- (2)异步(在子线程中执行任务):dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
1. Dispatch queue
在执行任务的时候存在两种dispatch queue
(1) Concurrent Dispatch Queue
- 并发队列(Concurrent Dispatch Queue): 多个任务同时执行,会自动开启多个线程同时执行任务
- 并发队列:GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列- 全局并发队列的优先级:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
(2) Serial Dispatch Queue
- 串行队列(Serial Dispatch Queue):一个任务执行完毕后,再执行下一个任务
- 串行队列获取:
- 使用dispatch_queue_create函数创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.huodada", NULL); - 使用主队列(跟主线程相关联的队列):主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行.使用dispatch_get_main_queue()获得主队列
- 使用dispatch_queue_create函数创建串行队列
(3) 同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)
2. 开发中常用API
0 dispatch_queue_create
//参数:指定队列的名称,推荐使用应用程序ID这种逆序全程域名,该名称在xcode和instruments的调试器中作为dispatch queue名称表示
dispatch_queue_t queue = dispatch_queue_create("com.hudada", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"block on queue");
});
- Concurrent Dispatch Queue并行执行多个追加处理,而Serial Dispatch Queue只能执行一个追加处理.当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行,也就是说一个串行队列只能处理一个追加的任务,当有多个串行队列的时候,各个串行队列之间是并发的
- 创建串行队列:dispatch_queue_t queue = dispatch_queue_create("com.hudada", DISPATCH_QUEUE_SERIAL);
- 创建并发队列:dispatch_queue_t q = dispatch_queue_create("com.hudada", DISPATCH_QUEUE_CONCURRENT);
1 dispatch_async
(1) 异步函数 + 并发队列:开启多条线程,并发执行任务
// 1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.将任务加入队列
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"1-----%@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"2-----%@", [NSThread currentThread]);
}
});
(2) 异步函数 + 串行队列:会开启新的线程,但是任务是串行的,执行完一个任务,再执行下一个任务
// 1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.hudada", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue = dispatch_queue_create("com.hudada", NULL);
// 2.将任务加入队列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
(3) 异步函数 + 主队列:只在主线程中执行任务
// 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.将任务加入队列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
2 dispatch_sync
(1)同步函数 + 并发队列:不会开启新的线程
// 1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
(2)同步函数 + 串行队列:不会开启新的线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务
// 1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.hudada", DISPATCH_QUEUE_SERIAL);
// 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
(3) 同步函数+主队列:不开线程,串行执行任务
// 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
3 dispatch_after
在项目的开发过程当中经常使用,比如想在几秒之后执行性处理:
//1.dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch queue.
//2.dispatch_time函数:获取从第一个参数DISPATCH_TIME_NOW指定的时间开始到第二个参数指定的秒单位时间后的时间
//3.#define NSEC_PER_SEC 1000000000ull "ull"是C语言的数值字面量,是显示表明类型时使用的字符串即: "unsigned long long"
//4.dispatch_time函数用于计算相对时间
//5.dispatch_walltime用于计算绝对时间,比如用于闹钟时间的计算等
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2秒之后执行");
});
- (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date {
double second,subsecond;
struct timespec time;
NSTimeInterval interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
dispatch_time_t milestone = dispatch_walltime(&time, 0);
return milestone;
}
4 dispatch_group_create
比如利用SDWebImage下载多图,等全部下载完成之后缓存起来:
class func cacheStatusImages(list: [Status], finished: (models:[Status]?, error:NSError?)->()) {
if list.count == 0
{
finished(models: list, error: nil)
return
}
// 1.创建一个组
let group = dispatch_group_create()
// 1.缓存图片
for status in list
{
// 1.1判断当前微博是否有配图, 如果没有就直接跳过
// Swift2.0新语法, 如果条件为nil, 那么就会执行else后面的语句
guard let _ = status.pictureURLS else
{
continue
}
for url in status.pictureURLS!
{
// 将当前的下载操作添加到组中
dispatch_group_enter(group)
// 缓存图片
SDWebImageManager.sharedManager().downloadImageWithURL(url, options: SDWebImageOptions(rawValue: 0), progress: nil, completed: { (_, _, _, _, _) -> Void in
// 离开当前组
dispatch_group_leave(group)
})
}
}
// 2.当所有图片都下载完毕再通过闭包通知调用者
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
// 能够来到这个地方, 一定是所有图片都下载完毕
finished(models: list, error: nil)
}
}
再比如在开发的过程当中可能会碰到这样的需求,等其他的操作都完成之后再做下一个操作,二其他的操作不确定什么时候完成的时候,此时就可以使用dispatch_group_async,例如网络请求异步操作下载两张图片,等两张图片都下载完成之后将两张图片合并为一张新的图片
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建一个队列组
dispatch_group_t group = dispatch_group_create();
// 1.下载图片1
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image1 = [UIImage imageWithData:data];
});
// 2.下载图片2
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image2 = [UIImage imageWithData:data];
});
// 3.将图片1、图片2合成一张新的图片
dispatch_group_notify(group, queue, ^{
// 开启新的图形上下文
UIGraphicsBeginImageContext(CGSizeMake(100, 100));
// 绘制图片
[self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
[self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
// 取得上下文中的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 结束上下文
UIGraphicsEndImageContext();
// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
// 4.将新图片显示出来
self.imageView.image = image;
});
});
另外在dispatch group 中可以使用dispatch_group_wait函数等待全部处理执行结束
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_async(queue, ^{
NSLog(@"任务一");
});
dispatch_async(queue, ^{
NSLog(@"任务二");
});
dispatch_async(queue, ^{
NSLog(@"任务三");
});
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
//会使当前的线程处于等待的状态,也就是说如果是在主线程执行dispatch_group_wait,在上面的Block执行完之前,主线程会处于卡死的状态。第二个参数是指定超时的时间,如果指定为DISPATCH_TIME_FOREVER(如上面这个例子)则表示会永久等待,直到上面的Block全部执行完,
//dispatch_group_wait函数的返回值若不为0,说明某一个处理还在进行中,若为0说明全部处理完毕
//第二个参数指定的为等待的时间
//如果设置为DISPATCH_TIME_FOREVER,意味着永久等待,只要处理还没有执行结束,就会一直等待,中途不能取消
//如果设置为DISPATCH_TIME_NOW,则不用任何等待判定是否执行结束
long result = dispatch_group_wait(group, time);
if (result == 0) {
//全部处理执行结束
NSLog(@"全部处理执行结束");
}else{
//某一个处理还在进行中
NSLog(@"某一个处理还在进行中");
}
5 dispatch_once
开发中设计单例的时候经常用到.下面为用宏定义实现的单例,新建一个.h文件,一个复制粘贴,就可以使用了
#define singleH(name) +(instancetype)share##name;
#if __has_feature(objc_arc)
#define singleM(name) static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)share##name\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}
#else
#define singleM static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)shareTools\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(oneway void)release\
{\
}\
\
-(instancetype)retain\
{\
return _instance;\
}\
\
-(NSUInteger)retainCount\
{\
return MAXFLOAT;\
}
#endif
5 dispatch semaphore
当并行执行一些数据操作的时候,会产生数据不一致的情况,这个时候就要做好排他控制,即一个线程在执行一段关键代码的时候其他的线程为保证数据操作的安全使其处于等待状态.此时可以使用信号量. 做法是 :当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待前面的线程释放信号量。当信号计数大于0时每条进来的线程使计数减1,直到变为0,变为0后其他的线程将进不来处于等待的状态,执行完任务的线程释放信号,使计数加1,如此循环....
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//参数表示计数的初始值
dispatch_semaphore_t semaphone = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < 100; i++)
{
dispatch_async(queue, ^{
//等待dispatch semaphone,直到dispatch semaphone的计数值达到大于等于1
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphone, time);
NSLog(@"%zd----result=%zd",i,result);
//由于dispatch semaphone的计数值达到大于等于1,所以将dispatch semaphone的值减去1,dispatch_semaphore_wait函数执行返回
//执行到此的时候dispatch semaphone的计数值恒为0,此时只有一个,因此可安全的做操作
[array addObject:[NSNumber numberWithInt:i]];
//排他控制处理结束,通过dispatch_semaphore_signal将计数值增加1
dispatch_semaphore_signal(semaphone);
});
}
6 dispatch_barrier_async
在队列执行的任务当中增加"栅栏",在增加"栅栏"之前已经开始执行的任务会继续执行,当执行dispatch_barrier_async的时候其他的block处于等待的状态,dispatch_barrier_async任务执行完之后,其后的block才会执行.
dispatch_queue_t queue = dispatch_queue_create("com.huoddada", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
7 dispatch_apply
dispatch_apply会将一个指定的block执行指定的次数,比如要对某个数组中的所有元素执行同样的block的时候
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSString *from = @"/Users/zhaodajun/Desktop/From";
NSString *to = @"/Users/zhaodajun/Desktop/To";
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subpaths = [mgr subpathsAtPath:from];
//第一个参数为重复的次数,第二个参数为追加对象的dispatch queue 第三个参数为追加的处理
dispatch_apply(subpaths.count, queue, ^(size_t index) {
NSString *subpath = subpaths[index];
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
// 剪切
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
NSLog(@"%@---%@", [NSThread currentThread], subpath);
});
8 dispatch_suspend / dispatch_resume
//暂停
dispatch_suspend(globalQueue)
//恢复
dispatch_resume(globalQueue)