1.GCD
GCD(Grand Central Dispatch) 伟大的中枢调度器
GCD是苹果为多核的并行运算提出的方案
GCD会充分利用CPU内核,会自动管理自己的生命周期
GCD 函数分为同步和异步
同步函数为 dispatch_sync
异步函数为 dispatch_async
1.1 核心概念
GCD 分为任务,队列
1.1.1.定制任务
1.1.2.将任务添加到队列中(GCD会自动将队列中的任务取出,放在对应的线程来执行)
1.2 队列类型
1.并发和串行
1.1 并发是指多个任务同时进行(自动开启多个线程执行)
1.2 并发功能只能在异步函数中进行
2.串行队列
让任务一个接一个来执行(上个任务完成后才接着进行下一个)
2.容易混淆的术语
同步和异步 : 主要区别是,是否具有开线程的能力
并发和串行 : 这两个是队列,是执行任务的方式
3 .GCD常用的几种方法
3.1 异步并行(这种方式可以开启线程,同时执行多个任务)
#pragma mark - 异步并行队列
-(void)asyncConcurrent{
// 创建队列
// 第一个参数是标识,第二个是并行队列
// dispatch_queue_t queue = dispatch_queue_create("cnw", DISPATCH_QUEUE_CONCURRENT);
// 创建全局队列,第一个参数是队列的优先级,这个是获得队列,就已经存在
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 封装任务,把任务放在队列中执行
dispatch_async(queue, ^{
NSLog(@"1-------当前队列%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-------当前队列%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-------当前队列%@",[NSThread currentThread]);
});
}
3.2 异步串行(可以开启线程,不过是开启一条线程,任务有序执行)
#pragma mark - 异步串行队列
-(void)asyncSerial
{
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("cnw0", DISPATCH_QUEUE_SERIAL);
// 封装任务
dispatch_async(queue, ^{
NSLog(@"1-------当前队列%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-------当前队列%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-------当前队列%@",[NSThread currentThread]);
});
}
3.3 同步串行(这种方法不会开启线程,任务有序执行)
#pragma mark - 同步串行队列
-(void)syncSerial
{
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("cnw0", DISPATCH_QUEUE_SERIAL);
// 封装任务
dispatch_sync(queue, ^{
NSLog(@"1-------当前队列%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-------当前队列%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-------当前队列%@",[NSThread currentThread]);
});
}
3.4 同步并行(不会开启线程,任务有序执行)
#pragma mark - 同步并行队列
-(void)syncConcurrent
{
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("cnw0", DISPATCH_QUEUE_CONCURRENT);
// 封装任务
dispatch_sync(queue, ^{
NSLog(@"1-------当前队列%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-------当前队列%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-------当前队列%@",[NSThread currentThread]);
});
}
4.主队列的特殊性
当前队列为主队列时,异步执行不会创建线程.
有主队列时,执行方法是在主线程中执行的,不会在子线程中执行
#pragma mark - 异步主队列
-(void)asyncMain
{
dispatch_queue_t queue = dispatch_get_main_queue();
// 封装任务,把任务放在队列中执行
dispatch_async(queue, ^{
NSLog(@"1-------当前队列%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-------当前队列%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-------当前队列%@",[NSThread currentThread]);
});
}
打印结果
2017-03-24 15:02:48.962 GCD[10228:591925] 1-------当前队列{number = 1, name = main}
2017-03-24 15:02:48.962 GCD[10228:591925] 2-------当前队列{number = 1, name = main}
2017-03-24 15:02:48.962 GCD[10228:591925] 3-------当前队列{number = 1, name = main}
当在同步函数中执行主队列时会出现死锁
#pragma mark - 同步主队列
-(void)syncMain
{
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"开始执行");
// 封装任务,把任务放在队列中执行
dispatch_sync(queue, ^{
NSLog(@"1-------当前队列%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-------当前队列%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-------当前队列%@",[NSThread currentThread]);
});
NSLog(@"完成执行");
}
原因是当执行主队列的任务时,会在主线程执行,而当前是同步函数,任务是一个完成后接着下一个,所以当进行第一个线程的时候,会在主线程执行,而此时主线程在等这个任务完成.
主队列特点:如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,知道主线程空闲为止.
如果在子线程中执行同步函数则可以有序执行任务
最后附上一张表
- GCD线程间的通信
还是下载图片,当下载完图片以后,回到主线程,只需要是主队列就会在主线程执行
这时候同步回到主线程是不会出现死锁的,因为这一步操作是在子线程中操作的
#pragma mark - GCD线程间通信
-(void)threadMes
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1490352377321&di=57b814f647287d559e264311887c5e9c&imgtype=0&src=http%3A%2F%2Fimg.sj33.cn%2Fuploads%2Fallimg%2F201402%2F102J0KJ-24.jpg"]]];
// 加载好数据以后,在UI界面显示回到主线程
// dispatch_sync(dispatch_get_main_queue()
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"当前线程是00%@",[NSThread currentThread]);
});
NSLog(@"当前线程是01%@",[NSThread currentThread]);
});
}
6.GCD的两个函数
6.1 延时操作
#pragma mark - GCD延时操作
-(void)delay
{
NSLog(@"开始任务");
// 第一种方法
// [self performSelector:@selector(run) withObject:self afterDelay:2.0];
// 第二种方法
// [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 第三种方法GCD
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
// 可以在主线程,也可以现在子线程
NSLog(@"%@",[NSThread currentThread]);
});
}
-(void)run
{
NSLog(@"%@",[NSThread currentThread]);
}
6.2 只会创建一次(创建单例时会用到)
#pragma mark - GCD只会创建一次的操作
-(void)once
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"打印了几次");
});
}
7.栅栏函数
根据表面意思就知道,栅栏:拦截的意思,就是等上边的任务执行完成以后,才可以往下进行
但栅栏函数的使用队列,不能是全局队列,不然是没效果的
#pragma mark - 栅栏函数
-(void)railings
{
dispatch_queue_t queue = dispatch_queue_create("cnw01", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"thread1--------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"thread3--------%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"thread2--------%@",[NSThread currentThread]);
});
}
8 . GCD的快速迭代(剪切文件)
#pragma mark - 快速迭代
-(void)apply
{
// 首先获得需要剪切的文件夹
NSString * frome = @"/Users/chenningwei/Desktop/屏幕截图";
// 剪切到的文件夹
NSString * to = @"/Users/chenningwei/Desktop/目标文件";
// 得到需要剪切文件夹内的元素
NSArray * files = [[NSFileManager defaultManager] subpathsAtPath:frome];
// 遍历数组中的元素
NSInteger count = files.count;
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {
// 获得剪切文件内文件的全路径
NSString * fromePath = [frome stringByAppendingPathComponent:files[index]];
// 剪切到文件夹文件全路径
NSString * toPath = [to stringByAppendingPathComponent:files[index]];
// 剪切
[[NSFileManager defaultManager] moveItemAtPath:fromePath toPath:toPath error:nil];
NSLog(@"%@----%@-----%@",fromePath,toPath,[NSThread currentThread]);
});
}
9 . 队列组
队列组执行是可以等上边操作完成以后,来进行下一步的操作,这是一般的异步操作实现不了了(目前我的水平)
比如:下载两张图片后,进行合成
只有两张图片下载完成以后才可以进行合并,而普通的异步操作是无序的实现不了,就需要用到队列组来实现,下面看代码
#pragma mark - 队列组下载图片
-(void)queues
{
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 全局队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 下载第一张图片
dispatch_group_async(group, queue, ^{
NSURL * url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1490352377321&di=57b814f647287d559e264311887c5e9c&imgtype=0&src=http%3A%2F%2Fimg.sj33.cn%2Fuploads%2Fallimg%2F201402%2F102J0KJ-24.jpg"];
NSData * data = [NSData dataWithContentsOfURL:url];
self.image1= [UIImage imageWithData:data];
});
dispatch_group_async(group, queue, ^{
NSURL * url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1490352377321&di=57b814f647287d559e264311887c5e9c&imgtype=0&src=http%3A%2F%2Fimg.sj33.cn%2Fuploads%2Fallimg%2F201402%2F102J0KJ-24.jpg"];
NSData * data = [NSData dataWithContentsOfURL:url];
self.image2= [UIImage imageWithData:data];
});
// 等两张图片下载完以后进行合成
dispatch_group_notify(group, queue, ^{
// 开始图形上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//画图
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
self.image1 = nil;
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
self.image2 = nil;
// 根据图形上下文得到一张图片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭图形上下文
UIGraphicsEndPDFContext();
// 设置图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView1.image = image;
});
});
}
10 . ARC环境下的单例模式
.1 首先定义一个全局变量
static Tool * _instance;
.2 重写allocWithZone方法
+(instancetype)allocWithZone:(struct _NSZone *)zone
在这方法里创建单例
// 第一种懒加载方法
// 防止多条线程共享一块资源出现的安全问题,加互斥锁
@synchronized(self){
if ( _instance == nil ){
_instance = [super allocWithZone:zone];
}
}
return _instance;
// 第二种利用GCD创建
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
// 在.h文件中可以定义个类方法,供外界调用,在.m文件中的实现方法
+(instancetype)shareTool
{
return [[self alloc] init];
}
// 但是为了安全起见,会重写
-(id)copyWithZone:(NSZone *)zone
{
return _instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}