GCD
GCD 核心概念
将
任务
添加到队列
,并且指定执行任务的函数
任务使用
block
封装
任务的
block
没有参数也没有返回值
执行任务的函数
必须等待当前语句执行完毕,才会执行下一条语句
不会开启线程
在当前执行
block
的任务
不用等待当前语句执行完毕,就可以执行下一条语句
会开启线程执行
block
的任务异步
是多线程的代名词
异步
dispatch_async
同步
dispatch_sync
队列 - 负责调度任务
专门用来在主线程上调度任务的队列
不会开启线程
在
主线程空闲时
才会调度队列中的任务在主线程执行dispatch_get_main_queue();
一次可以"调度"多个任务
dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
一次只能"调度"一个任务
dispatch_queue_create("itheima", NULL);
串行队列
并发队列
主队列
阶段性小结
开不开线程由
执行任务的函数
决定异步
开,异步
是多线程的代名词同步
不开
开几条线程由
队列
决定iOS 8.0 之后,GCD 能够开启非常多的线程
iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
串行队列
开一条线程并发队列
开多条线程,具体能开的线程数量由底层线程池决定
- 队列的选择
多线程的目的:将耗时的操作放在后台执行!
串行队列,只开一条线程,所有任务顺序执行
如果任务有先后执行顺序的要求
效率低 -> 执行慢 -> "省电"
有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"
并发队列,会开启多条线程,所有任务不按照顺序执行
如果任务没有先后执行顺序的要求
效率高 -> 执行快 -> "费电"
WIFI,包月
实际开发中,线程数量如何决定?
WIFI 线程数
6
条3G / 4G 移动开发的时候,
2~3
条,再多会费电费钱!
同步 & 异步
概念
同步
必须等待当前语句执行完毕,才会执行下一条语句
异步
不用等待当前语句执行完毕,就可以执行下一条语句
NSThread
中的 同步
& 异步
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"start"); // 同步执行// [self demo];
// 异步执行
[self performSelectorInBackground:@selector(demo) withObject:nil]; NSLog(@"over");
}
- (void)demo { NSLog(@"%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0]; NSLog(@"demo 完成");
}
代码小结
同步
从上到下顺序执行异步
是多线程的代名词
block
概念
block 是 C 语言的
是一种数据类型,可以当作参数传递
是一组预先准备好的代码,在需要的时候执行
动画 block 回顾
self.demoView.center = CGPointMake(self.view.center.x, 0);// 此方法会立即执行动画 block[UIView animateWithDuration:2.0 delay:0 usingSpringWithDamping:0.3 initialSpringVelocity:10 options:0 animations:^{ NSLog(@"动画开始"); self.demoView.center = self.view.center;
} completion:^(BOOL finished) { // 会在动画结束后执行
NSLog(@"动画完成");
}];NSLog(@"come here");
block 基本演练
最简单的
block
- (void)blockDemo1 { // 定义block
// 类型 变量名 = 值
void (^block)() = ^ { NSLog(@"Hello block");
}; // 执行
block();
}
使用
inlineBlock
可以快速定义block
,不过block
一定要过关
当作参数传递
- (void)blockDemo2 { void (^block)() = ^ { NSLog(@"Hello block");
};
[self demoBlock:block];
}/// 演示 block 当作参数传递- (void)demoBlock:(void (^)())completion { NSLog(@"干点什么");
completion();
}
使用局部变量
- (void)blockDemo3 { // 栈区变量
int i = 10; NSLog(@"%p", &i); void (^block)() = ^ { // 定义 block 的时候会对栈区变量进行一次 copy
NSLog(@"Hello block %d %p", i, &i);
};
[self demoBlock:block];
}
如果 block 中使用了外部变量,会对外部变量做一次
copy
在 block 中修改外部变量
- (void)blockDemo4 { // 栈区变量
__block int i = 10; NSLog(@"%p", &i); void (^block)() = ^ { // 定义 block 的时候会对栈区变量进行一次 copy
NSLog(@"Hello block %d %p", i, &i);
i = 20;
}; NSLog(@"block 定义完成 %p %d", &i, i);
[self demoBlock:block]; NSLog(@"===>%d", i);
}
如果要在 block 内部修改栈区变量,需要使用
__block
修饰符,并且定义 block 之后,栈区变量的地址会变化为堆区地址
block 的内存位置
全局区:如果block中没有使用任何全局变量
栈区:如果 block 中使用了外部变量
MRC 模式可以看到
ARC 模式,系统会自动将 Block 复制到堆中
堆区:将 block 设置给 copy 属性
@property (nonatomic, copy) void (^myBlock)();
- (void)blockDemo5 { int i = 10; void (^block)() = ^ { NSLog(@"i --- %d", i);
}; NSLog(@"%@", block); self.myBlock = block;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"%@", self.myBlock);
}
注意:虽然目前 ARC 编译器在设置属性时,已经替程序员复制了 block,但是定义
block
时,仍然建议使用copy
属性
GCD 常用代码
体验代码
异步执行任务
- (void)gcdDemo1 { // 1. 全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 2. 任务
void (^task)() = ^ { NSLog(@"%@", [NSThread currentThread]);
}; // 3. 指定执行任务的函数
// 异步执行任务 - 新建线程,在新线程执行 task
dispatch_async(q, task); NSLog(@"come here");
}
注意:如果等待时间长一些,会发现线程的
number
发生变化,由此可以推断gcd 底层线程池
的工作
同步执行任务
- (void)gcdDemo1 { // 1. 全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 2. 任务
void (^task)() = ^ { NSLog(@"%@", [NSThread currentThread]);
}; // 3. 指定执行任务的函数
// 同步执行任务 - 不开启线程,在当前线程执行 task
dispatch_sync(q, task); NSLog(@"come here");
}
精简代码
- (void)gcdDemo2 { for (int i = 0; i < 10; ++i) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"%@ %@", [NSThread currentThread], @"hello");
});
}
}
与 NSThread
的对比
所有的代码写在一起的,让代码更加简单,易于阅读和维护
NSThread
通过@selector
指定要执行的方法,代码分散GCD
通过block
指定要执行的代码,代码集中
使用 GCD
不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期
如果要开多个线程 NSThread
必须实例化多个线程对象
NSThread
靠 NSObject
的分类方法实现的线程间通讯,GCD
靠 block
线程间通讯
- (void)gcdDemo3 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"耗时操作 %@", [NSThread currentThread]); // 耗时操作之后,更新UI
dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"更新 UI %@", [NSThread currentThread]);
});
});
}
以上代码是
GCD
最常用代码组合!
如果要在更新 UI 之后,继续做些事情,可以使用以下代码
- (void)gcdDemo4 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"耗时操作"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"更新UI");
}); NSLog(@"更新UI完毕");
});
}
网络下载图片
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%s %@", __FUNCTION__, [NSThread currentThread]);
// 1. 异步下载网络图片
NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/1f178a82b9014a901bef674aaa773912b21bee70.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2. 完成后更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
[self.imageView sizeToFit];
self.scrollView.contentSize = image.size;
});
});
}
串行队列
特点
以
先进先出
的方式,顺序
调度队列中的任务执行无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务
队列创建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);
串行队列演练
串行队列 同步执行
/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo1 { // 1. 队列
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL); // 2. 执行任务
for (int i = 0; i < 10; ++i) { NSLog(@"--- %d", i); dispatch_sync(q, ^{ NSLog(@"%@ - %d", [NSThread currentThread], i);
});
} NSLog(@"come here");
}
串行队列 异步执行
/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo2 { // 1. 队列
dispatch_queue_t q = dispatch_queue_create("itheima", NULL); // 2. 执行任务
for (int i = 0; i < 10; ++i) { NSLog(@"--- %@ %d", [NSThread currentThread], i); dispatch_async(q, ^{ NSLog(@"%@ - %d", [NSThread currentThread], i);
});
} NSLog(@"come here");
}
并发队列
特点
以
先进先出
的方式,并发
调度队列中的任务执行如果当前调度的任务是
同步
执行的,会等待任务执行完成后,再调度后续的任务如果当前调度的任务是
异步
执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行
队列创建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
并发队列演练
并发队列 异步执行
/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo3 { // 1. 队列
dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT); // 2. 执行任务
for (int i = 0; i < 10; ++i) { dispatch_async(q, ^{ NSLog(@"%@ - %d", [NSThread currentThread], i);
});
} NSLog(@"come here");
}
并发队列 同步执行
/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo4 { // 1. 队列
dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT); // 2. 执行任务
for (int i = 0; i < 10; ++i) { dispatch_sync(q, ^{ NSLog(@"%@ - %d", [NSThread currentThread], i);
}); NSLog(@"---> %i", i);
} NSLog(@"come here");
}
主队列
特点
专门用来在主线程上调度任务的队列
不会开启线程
以
先进先出
的方式,在主线程空闲时
才会调度队列中的任务在主线程执行如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
队列获取
主队列是负责在主线程调度任务的
会随着程序启动一起创建
主队列只需要获取不用创建
dispatch_queue_t queue = dispatch_get_main_queue();
主队列演练
主队列,异步执行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self gcdDemo1];
[NSThread sleepForTimeInterval:1]; NSLog(@"over");
}
- (void)gcdDemo1 { dispatch_queue_t queue = dispatch_get_main_queue(); for (int i = 0; i < 10; ++i) { dispatch_async(queue, ^{ NSLog(@"%@ - %d", [NSThread currentThread], i);
}); NSLog(@"---> %d", i);
} NSLog(@"come here");
}
在
主线程空闲时
才会调度队列中的任务在主线程执行
主队列,同步执行
// MARK: 主队列,同步任务- (void)gcdDemo6 { // 1. 队列
dispatch_queue_t q = dispatch_get_main_queue(); NSLog(@"!!!"); // 2. 同步
dispatch_sync(q, ^{ NSLog(@"%@", [NSThread currentThread]);
}); NSLog(@"come here");
}
主队列
和主线程
相互等待会造成死锁
同步任务的作用
同步任务,可以让其他异步执行的任务,
依赖
某一个同步任务
例如:在用户登录之后,再异步下载文件!
- (void)gcdDemo1 { dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{ NSLog(@"登录 %@", [NSThread currentThread]);
}); dispatch_async(queue, ^{ NSLog(@"下载 A %@", [NSThread currentThread]);
}); dispatch_async(queue, ^{ NSLog(@"下载 B %@", [NSThread currentThread]);
});
}
代码改造,让登录也在异步执行
- (void)gcdDemo2 { dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT); void (^task)() = ^{ dispatch_sync(queue, ^{ NSLog(@"登录 %@", [NSThread currentThread]);
}); dispatch_async(queue, ^{ NSLog(@"下载 A %@", [NSThread currentThread]);
}); dispatch_async(queue, ^{ NSLog(@"下载 B %@", [NSThread currentThread]);
});
}; dispatch_async(queue, task);
}
主队列调度同步队列不死锁
- (void)gcdDemo3 { dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT); void (^task)() = ^ { dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"死?");
});
}; dispatch_async(queue, task);
}
主队列在主线程空闲时
才会调度队列中的任务在主线程执行
Barrier 异步
主要用于在多个异步操作完成之后,统一对
非线程安全
的对象进行更新适合于
大规模的 I/O
操作
代码演练
准备工作
@interface ViewController () { // 加载照片队列
dispatch_queue_t _photoQueue;
}@property (nonatomic, strong) NSMutableArray *photoList;@end- (NSMutableArray *)photoList { if (_photoList == nil) {
_photoList = [[NSMutableArray alloc] init];
} return _photoList;
}
NSMutableArray
是非线程安全的
viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
_photoQueue = dispatch_queue_create("com.itheima.com", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 20; ++i) {
[self loadPhotos:i];
}
}
模拟下载照片并在完成后添加到数组
- (void)loadPhotos:(int)index { dispatch_async(_photoQueue, ^{
[NSThread sleepForTimeInterval:1.0]; NSString *fileName = [NSString stringWithFormat:@"%02d.jpg", index % 10 + 1]; NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil]; UIImage *image = [UIImage imageWithContentsOfFile:path];
[self.photoList addObject:image]; NSLog(@"添加照片 %@", fileName);
});
}
运行测试
由于
NSMutableArray
是非线程安全的,如果出现两个线程在同一时间向数组中添加对象,会出现程序崩溃的情况解决办法
NSLog(@"添加照片 %@", fileName);
dispatch_barrier_async(_photoQueue, ^{
[self.photoList addObject:image]; NSLog(@"OK %@", [NSThread currentThread]);
});
使用
dispatch_barrier_async
添加的 block 会在之前添加的 block 全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!
Barrier 工作示意图
注意:
dispatch_barrier_async
必须使用自定义队列,否则执行效果和全局队列一致
全局队列
是系统为了方便程序员开发提供的,其工作表现与
并发队列
一致
全局队列 & 并发队列的区别
全局队列
没有名称
无论 MRC & ARC 都不需要考虑释放
日常开发中,建议使用"全局队列"
并发队列
有名字,和
NSThread
的name
属性作用类似如果在 MRC 开发时,需要使用
dispatch_release(q);
释放相应的对象dispatch_barrier
必须使用自定义的并发队列开发第三方框架时,建议使用并发队列
全局队列 异步任务
/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo8 { // 1. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 2. 执行任务
for (int i = 0; i < 10; ++i) { dispatch_async(q, ^{ NSLog(@"%@ - %d", [NSThread currentThread], i);
});
} NSLog(@"come here");
}
运行效果与并发队列相同
参数
服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级
DISPATCH_QUEUE_PRIORITY_HIGH
2 高优先级DISPATCH_QUEUE_PRIORITY_DEFAULT
0 默认优先级DISPATCH_QUEUE_PRIORITY_LOW
(-2) 低优先级DISPATCH_QUEUE_PRIORITY_BACKGROUND
INT16_MIN 后台优先级
QOS_CLASS_USER_INTERACTIVE
0x21, 用户交互(希望最快完成-不能用太耗时的操作)QOS_CLASS_USER_INITIATED
0x19, 用户期望(希望快,也不能太耗时)QOS_CLASS_DEFAULT
0x15, 默认(用来底层重置队列使用的,不是给程序员用的)QOS_CLASS_UTILITY
0x11, 实用工具(专门用来处理耗时操作!)QOS_CLASS_BACKGROUND
0x09, 后台QOS_CLASS_UNSPECIFIED
0x00, 未指定,可以和iOS 7.0 适配
iOS 8.0(新增,暂时不能用,今)
iOS 7.0
为未来保留使用的,应该永远传入0
结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0, 0);
延迟操作
// MARK: - 延迟执行- (void)delay { /**
从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码
参数
1. when 从现在开始,经过多少纳秒
2. queue 队列
3. block 异步执行的任务
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); void (^task)() = ^ { NSLog(@"%@", [NSThread currentThread]);
}; // 主队列// dispatch_after(when, dispatch_get_main_queue(), task);
// 全局队列// dispatch_after(when, dispatch_get_global_queue(0, 0), task);
// 串行队列
dispatch_after(when, dispatch_queue_create("itheima", NULL), task); NSLog(@"come here");
}
- (void)after {
[self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0]; NSLog(@"come here");
}
一次性执行
有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”
// MARK: 一次性执行- (void)once { static dispatch_once_t onceToken; NSLog(@"%ld", onceToken); dispatch_once(&onceToken, ^{
[NSThread sleepForTimeInterval:1.0]; NSLog(@"一次性吗?");
}); NSLog(@"come here");
}
dispatch 内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的
以下代码用于测试多线程的一次性执行
- (void)demoOnce { for (int i = 0; i < 10; ++i) { dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self once];
});
}
}
单例测试
单例的特点
在内存中只有一个实例
提供一个全局的访问点
单例实现
// 使用 dispatch_once 实现单例+ (instancetype)sharedSingleton { static id instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
}); return instance;
}// 使用互斥锁实现单例+ (instancetype)sharedSync { static id syncInstance; @synchronized(self) { if (syncInstance == nil) {
syncInstance = [[self alloc] init];
}
} return syncInstance;
}
面试时只要实现上面
sharedSingleton
方法即可
单例测试
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
long largeNumber = 1000 * 1000;
// 测试互斥锁
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i) {
[Singleton sharedSync];
}
NSLog(@"互斥锁: %f", CFAbsoluteTimeGetCurrent() - start);
// 测试 dispatch_once
start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i) {
[Singleton sharedSingleton];
}
NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);
}
调度组
常规用法
- (void)group1 { // 1. 调度组
dispatch_group_t group = dispatch_group_create(); // 2. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 3. 将任务添加到队列和调度组
dispatch_group_async(group, q, ^{
[NSThread sleepForTimeInterval:1.0]; NSLog(@"任务 1 %@", [NSThread currentThread]);
});
dispatch_group_async(group, q, ^{ NSLog(@"任务 2 %@", [NSThread currentThread]);
});
dispatch_group_async(group, q, ^{ NSLog(@"任务 3 %@", [NSThread currentThread]);
}); // 4. 监听所有任务完成
dispatch_group_notify(group, q, ^{ NSLog(@"OVER %@", [NSThread currentThread]);
}); // 5. 判断异步
NSLog(@"come here");
}
enter & leave
// MARK: - 调度组 2- (void)group2 { // 1. 调度组
dispatch_group_t group = dispatch_group_create(); // 2. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0); // dispatch_group_enter & dispatch_group_leave 必须成对出现
dispatch_group_enter(group);
dispatch_group_async(group, q, ^{ NSLog(@"任务 1 %@", [NSThread currentThread]); // dispatch_group_leave 必须是 block 的最后一句
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, q, ^{ NSLog(@"任务 2 %@", [NSThread currentThread]); // dispatch_group_leave 必须是 block 的最后一句
dispatch_group_leave(group);
}); // 4. 阻塞式等待调度组中所有任务执行完毕
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // 5. 判断异步
NSLog(@"OVER %@", [NSThread currentThread]);
}