GCD的基本使用
需要经常使用的两个函数和队列
- 同步函数
dispatch_sync
:立刻马上执行,如果没有执行完毕,后面的也不会执行- 异步函数
dispatch_async
:如果没有执行完毕,后面的也可以执行- 并发队列
DISPATCH_QUEUE_CONCURRENT
- 串行队列
DISPATCH_QUEUE_SERIAL
创建队列方法
- 方法1:自己创建一个新的线程
/**
第一个参数: C语言的字符串,标签
第二个参数: 队列的类型
DISPATCH_QUEUE_CONCURRENT:并发队列
DISPATCH_QUEUE_SERIAL:串行队列
*/
dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
- 方法二:获得全局并发队列 且并不是一个任务一条线程,是系统随机给你分配线程
// 获得全局并发队列:并不是一个任务一条线程,而是系统给你分配的线程
/**
第一个参数: 优先级
第二个参数: 未来使用的,默认 0
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
封装任务方法
/**
第一个参数: 队列
第二个参数: 要执行的任务
*/
- 方法一:异步函数
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
- 方法二:同步函数
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
异步函数+并发队列
会开启多条线程,队列中的任务是并发(异步)执行 无顺序
// 异步函数+并发队列:会开启多条线程,队列中的任务是并发(异步)执行无顺序
- (void) asyncConcurrent{
// 1.创建队列
/**
第一个参数: C语言的字符串,标签
第二个参数: 队列的类型
DISPATCH_QUEUE_CONCURRENT:并发队列
DISPATCH_QUEUE_SERIAL:串行队列
*/
// dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
// 获得全局并发队列:并不是一个任务一条线程,而是系统给你分配的线程
/**
第一个参数: 优先级
第二个参数: 未来使用的,默认 0
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2 .封装任务 -> 添加任务到队列中
/**
第一个参数: 队列
第二个参数: 要执行的任务
*/
dispatch_async(queue, ^{
NSLog(@"asyncConcurrent1------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"asyncConcurrent2------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"asyncConcurrent3------%@",[NSThread currentThread]);
});
}
异步�函数+串行队列
只会开启一条线程,任务是串行执行有顺序
// 异步�函数+串行队列:只会开启一条线程,任务是串行执行有顺序
- (void) asyncSerial{
//1. 创建队列
dispatch_queue_t queue = dispatch_queue_create("asyncSerial", DISPATCH_QUEUE_SERIAL);
// 2. 封装任务
dispatch_async(queue, ^{
NSLog(@"asyncSerial1------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"asyncSerial2------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"asyncSerial3------%@",[NSThread currentThread]);
});
}
同步函数+并发队列
不会开启线程,任务是串行执行
// 同步函数+并发队列:不会开启线程,任务是串行执行
- (void) syncConcurrent{
// 1. 创建队列
dispatch_queue_t queue = dispatch_queue_create("syncConcurrent", DISPATCH_QUEUE_CONCURRENT);
// 2. 封装任务
dispatch_sync(queue, ^{
NSLog(@"syncConcurrent1------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"syncConcurrent2------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"syncConcurrent3------%@",[NSThread currentThread]);
});
}
同步函数+串行队列
不会开启线程,任务是串行执行
// 同步函数+串行队列:不会开启线程,任务是串行执行
- (void) syncSerial{
// 1. 创建队列
dispatch_queue_t queue = dispatch_queue_create("syncSerial", DISPATCH_QUEUE_SERIAL);
// 2. 封装任务
dispatch_sync(queue, ^{
NSLog(@"syncSerial1------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"syncSerial2------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"syncSerial3------%@",[NSThread currentThread]);
});
}
特别的队列----主队列
特点:如果主队列中发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到主线程空闲为止
异步函数+主队列
所有任务都在主线程中执行,不会开启线程
- (void) asyncMain{
// 1. 获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.封装任务 - 异步函数
dispatch_async(queue, ^{
NSLog(@"asyncMain1------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"asyncMain2------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"asyncMain3------%@",[NSThread currentThread]);
});
}
同步函数+主队列
死锁 如果该方法在子线程中执行,那么所有的任务都会在主线程中执行
// 同步函数+主队列:死锁
// 注意:如果该方法在子线程中执行,那么所有的任务都会在主线程中执行
- (void) syncMain{
// 1. 获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.封装任务 - 同步函数
dispatch_sync(queue, ^{
NSLog(@"syncMain1------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"syncMain2------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"syncMain3------%@",[NSThread currentThread]);
});
}
备注
dispatch_async
和dispatch_async_f
的区别在于它们封装任务的方法,dispatch_async
使用的是block而dispatch_async_f
使用的则是C语言函数,其它是没有任何区别的,鉴于_f
用得比较少 这里只做简单的了解
- (void) note{
// 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 区别:封装任务的方法(block 函数)
int context = 10;
/**
* 第一个参数:队列
第二个参数:参数 函数指针 注意传地址 不要传值
第三个参数:要调用函数名称 C语言函数
*/
dispatch_async_f(queue, &context, task);
}
void task (void * context){
int * c = context;
NSLog(@"%d",*c);
}
GCD线程间的通信
这里以下载网络图片为例子
- (void) downloadImage{
// 创建子线程下载图片
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 1. 确定图片url
NSURL * url = [NSURL URLWithString:@"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1909600733,2304577724&fm=26&gp=0.jpg"];
// 2.下载图片二进制数据到本地
NSData * imageData = [NSData dataWithContentsOfURL:url];
// 3.转换图片
UIImage * image = [UIImage imageWithData:imageData];
NSLog(@"当前线程------%@",[NSThread currentThread]);
// 更新UI 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"当前线程------%@",[NSThread currentThread]);
});
});
}
GCD的常用函数
延迟执行
dispatch_after
GCD 可以自由选择在哪个线程执行
// 方法3. GCD 可以自由选择在哪个线程执行
// dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
/**
* 第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
第一个参数:延迟2.0 GCD时间单位:纳秒 (需要 * 10的9次方 转换为秒)
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
NSLog(@"GCD延迟执行------%@",[NSThread currentThread]);
NSLog(@"------GCDend------");
});
一次性代码
dispatch_once
注意:不能够在懒加载中使用
// 一次性代码
- (void) once{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"------noce------");
});
}
栅栏函数
dispatch_barrier_async
当之前的任务执行完成之后才会执行后面的任务 栅栏函数不能使用全局并发队列
// 1 获取全局并发队列
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
// 1. 异步函数
dispatch_async(queue, ^{
NSLog(@"download1------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3------%@",[NSThread currentThread]);
});
// 栅栏函数
// 栅栏函数不能使用全局并发队列
dispatch_barrier_async(queue, ^{
NSLog(@"+++++++++++++++++++++");
});
dispatch_async(queue, ^{
NSLog(@"download2------%@",[NSThread currentThread]);
});
GCD的快速迭代
dispatch_apply
因为在迭代的过程中会开启子线程 所以比一般的for
循环迭代要快速很多
// GCD快速迭代 :开启
- (void) apply{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
/**
* 第一个参数:遍历次数
第二个参数:队列(并发队列)
第三个参数:索引
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zd-----------%@",index,[NSThread currentThread]);
});
}
下面通过一个文件剪切的Demo来演示👇
- (void) moveFileWithGCD{
// 1. 拿到文件路径
NSString * form = @"/Users/ChangRJey/Desktop/修改前后对比图";
// 2. 获得目标文件路径
NSString * to = @"/Users/ChangRJey/Desktop/yoyoyo";
// 3. 得到目录下面的所有文件
NSArray * subPaths= [[NSFileManager defaultManager] subpathsAtPath:form];
NSLog(@"文件名------%@",subPaths);
// 4. 遍历所有文件,然后执行剪切操作
NSInteger count = subPaths.count;
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSString * fullPath = [form stringByAppendingPathComponent:subPaths[index]];
NSString * toFullPath = [to stringByAppendingPathComponent:subPaths[index]];
NSLog(@"文件名------%@",fullPath);
// 4.2 执行剪切操作
/**
* 第一个参数:要剪切的文件路径
第二个参数:文件应该被存放的目标路径
第三个参数:错误信息
*/
[[NSFileManager defaultManager] moveItemAtPath:fullPath toPath:toFullPath error:nil];
NSLog(@"%@------%@------%@",fullPath,toFullPath,[NSThread currentThread]);
});
}
GCD队列组的基本使用
创建Group
dispatch_group_t
完成通知
dispatch_group_notify
- 方法一 简化版
// 拦截通知,当队列组中所有任务都执行完成完毕之后会进行通知
// 内部本身是异步的
- (void) groupOne{
// 1. 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 创建队列组
dispatch_group_t group = dispatch_group_create();
// 3. 异步函数封装任务
dispatch_group_async(group, queue, ^{
NSLog(@"download_group1------%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"download_group2------%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"download_group3------%@",[NSThread currentThread]);
});
// 拦截通知,当队列组中所有任务都执行完成完毕之后会进行通知
// 内部本身是异步的
dispatch_group_notify(group, queue, ^{
NSLog(@"------dispatch_group_notify------");
});
}
- 方法二 以前版本
- (void) groupTwo{
// 1. 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 创建队列组
dispatch_group_t group = dispatch_group_create();
// 3. 在该方法后面的异步任务会被纳入到队列组的监听范围
// dispatch_group_enter | dispatch_group_leave 必须配对使用
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"download_group1------%@",[NSThread currentThread]);
// 离开队列组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"download_group2------%@",[NSThread currentThread]);
// 离开队列组
dispatch_group_leave(group);
});
// dispatch_group_notify(group, queue, ^{
// NSLog(@"------dispatch_group_notify------");
// });
// 等待
// DISPATCH_TIME_FOREVER:死等,知道队列组中所有任务执行完毕之后才能执行 本身是阻塞的
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"------end------");
}
示例
下面通过一个例子来实际使用GCD的队列组 通过下载 图片1 和 图片2 之后然后合并两张图片 因为合并两张图片必须得让子线程中的 图片1 和 图片2 下载完成之后才能够进行合并图片
- (void) groupThree{
/**
* 1. 下载图片1 开子线程
2. 下载图片2 开子线程
3. 合成图片并显示图片 开子线程
*/
// 获得并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 1. 下载图片1 开子线程
dispatch_group_async(group, queue, ^{
NSLog(@"download_image1------%@",[NSThread currentThread]);
// 1. 1 确定url
NSURL * url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502282744935&di=aaefb03a83917b6b94da36fbf69fa080&imgtype=0&src=http%3A%2F%2Fpic.ilitu.com%2Fy3%2F1475_39900960921.jpg"];
// 1.2 下载图片二进制数据到本地
NSData * imageData = [NSData dataWithContentsOfURL:url];
// 1.3 转换图片
self.image1 = [UIImage imageWithData:imageData];
});
// 2. 下载图片1 开子线程
dispatch_group_async(group, queue, ^{
NSLog(@"download_image2------%@",[NSThread currentThread]);
// 2. 1 确定url
NSURL * url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502282773716&di=9b788f0dda94c259067c2826795caef4&imgtype=0&src=http%3A%2F%2Fi3.sinaimg.cn%2Fgm%2F2015%2F0422%2FU3932P115DT20150422122125.jpg"];
// 2.2 下载图片二进制数据到本地
NSData * imageData = [NSData dataWithContentsOfURL:url];
// 2.3 转换图片
self.image2 = [UIImage imageWithData:imageData];
});
// 3. 合成图片并显示图片 开子线程
dispatch_group_notify(group, queue, ^{
NSLog(@"合并图片------%@",[NSThread currentThread]);
// 3.1 创建图形上下文
UIGraphicsBeginImageContext(CGSizeMake(300, 400));
// 3.2 画图1
[self.image1 drawInRect:CGRectMake(0, 0, 300, 200)];
self.image1 = nil;
// 3.3 画图2
[self.image2 drawInRect:CGRectMake(0, 200, 300, 200)];
self.image2 = nil;
// 3.4 根据上下文得到一张图片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
// 3.5 关闭上下文
UIGraphicsEndImageContext();
// 3.6 得到图片 回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"更新UI------%@",[NSThread currentThread]);
});
});
}
以上就是GCD所有的基本常用方法功能,希望能够帮助到大家,所有Demo我已经上传到GitHub,有喜欢的朋友记得给个Stare,谢谢!
另外分享一个觉得好用的已经封装好的GCD
我是Renjiee 我要做最骚的程序猿👨💻