一、消息循环
- 什么是消息循环
- Runloop就是消息循环,每一个线程内部都有一个消息循环
- 只有主线程的消息循环默认开启,子线程的消息循环默认不开启
- 消息循环的目的
- 保证程序不退出
- 负责处理输入事件
- 如果没有事件发生,会让程序进入休眠状态
1.1 消息循环-输入事件
- 输入事件
- Runloop接收输入事件来自两种不同的来源:输入源(input source)和定时源(timer source)
- input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects. An NSRunLoop object also processes NSTimer events.
1.2 消息循环-消息循环模式
- 通过定时器,复习消息循环的模式
- 定时器执行的方法中不易执行太耗时的操作,否则就会降低用户体验,用户拖拽的时候会感觉到卡顿的现象
- 消息循环的两种模式
- NSDefaultRunLoopMode、NSRunLoopCommonModes
- NSDefaultRunLoopMode
- The mode to deal with input sources other than NSConnection objects.
- This is the most commonly used run-loop mode.
- Available in iOS 2.0 and later.
- NSRunLoopCommonModes
- Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common" modes; see the description of CFRunLoopAddCommonMode for details.
1.3 注意
- 使用消息循环的时候必须指定两件事情
- 输入事件:输入源和定时源
- 消息循环模式
- 消息循环运行在某一种消息循环模式上
- 输入事件必须设置消息循环的模式,并且如果想让输入事件可以在消息循环上执行,输入事件的消息循环模式必须和当前消息循环的消息循环模式一致
- (void)viewDidLoad {
[super viewDidLoad];
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
//把定时器添加到当前线程消息循环中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//消息循环是在一个指定的模式下运行的 默认的模式NSDefaultRunLoopMode,设置的输入事件也需要指定一个模式,消息循环的模式必须和输入事件的模式匹配才会执行
//UITrackingRunLoopMode 当滚动scrollView的时候,消息循环的模式自动改变
}
- (void)demo {
//输出当前消息循环的模式
NSLog(@"hello %@",[NSRunLoop currentRunLoop].currentMode);
}
二、子线程的消息循环
- 主线程的消息循环默认开启,子线程的消息循环不会开启
- 启动子线程的消息循环
- [[NSRunLoop currentRunLoop] run];
- *线程池的实现原理,开启线程后永不销毁,当需要让子线程执行新的方法,使用performSelector让指定的方法在指定的子线程上运行
- (void)viewDidLoad {
[super viewDidLoad];
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
//把定时器添加到当前线程消息循环中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//消息循环是在一个指定的模式下运行的 默认的模式NSDefaultRunLoopMode,设置的输入事件也需要指定一个模式,消息循环的模式必须和输入事件的模式匹配才会执行
//UITrackingRunLoopMode 当滚动scrollView的时候,消息循环的模式自动改变
}
- (void)demo {
//输出当前消息循环的模式
NSLog(@"hello %@",[NSRunLoop currentRunLoop].currentMode);
}
三、GCD
- 什么是GCD
- 全称是Grand Central Dispatch
- 纯C语言,提供了非常多强大的函数
- GCD的优势
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// //1 创建队列
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// //2 创建任务
// dispatch_block_t task = ^{
// NSLog(@"hello %@",[NSThread currentThread]);
// };
// //3 异步执行
// dispatch_async(queue, task);
//简化用法
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"hello %@",[NSThread currentThread]);
});
}
异步下载网络图片
@interface ViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@end
@implementation ViewController
- (void)loadView {
self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.scrollView.backgroundColor = [UIColor whiteColor];
self.view = self.scrollView;
self.imageView = [[UIImageView alloc] init];
[self.scrollView addSubview:self.imageView];
}
- (void)viewDidLoad {
[super viewDidLoad];
//开启异步执行,下载网络图片
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSURL *url = [NSURL URLWithString:@"http://img02.tooopen.com/images/20141231/sy_78327074576.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:data];
//回到主线程更新UI
dispatch_sync(dispatch_get_main_queue(), ^{
self.imageView.image = img;
[self.imageView sizeToFit];
self.scrollView.contentSize = img.size;
});
});
}
3.1 任务和队列
- GCD的两个核心
- 任务:执行什么操作
- 队列:用来存放任务
- GCD使用的两个步骤
- 创建任务:确定要做的事情
- 将任务添加到队列中
- GCD会自动将队列中的任务取出,放到对应的线程中执行
- 任务的取出遵循队列的FIFO原则:先进先出,后进后出
3.2 执行任务的方式
- GCD中有2个用来执行任务的函数
- 同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
-
- 异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
3.3 队列的类型
- GCD的队列可以分为2大类型
- 并发队列(Concurrent Dispatch Queue)
- 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
- 并发功能只有在异步(dispatch_async)函数下才有效
- 串行队列(Serial Dispatch Queue)
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue)
3.4 总结
- 同步和异步决定了要不要开启新的线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力
- 并发和串行决定了任务的执行方式
- 并发:多个任务并发(同时)执行
- 串行:一个任务执行完毕后,再执行下一个任务
四、串行队列
- 串行队列,同步执行
- 不开线程,同步执行(在当前线程执行)
#define DISPATCH_QUEUE_SERIAL NULL
//串行队列
//dispatch_queue_t q = dispatch_queue_create("test", NULL);
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//同步执行
dispatch_sync(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
- 串行队列,异步执行
- 开一个线程,顺序执行 异步->开线程
//只有一个线程,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self demo2];
}
//1 串行队列,同步执行 不开新线程, 任务是按顺序执行
- (void)demo1 {
//串行队列
dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"hello %d %@",i,[NSThread currentThread]);
});
}
}
//2 串行队列,异步执行 开启新线程(1个),任务是有序执行
- (void)demo2 {
//串行队列
dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"hello %d %@",i,[NSThread currentThread]);
});
}
}
五、并行队列
- 并行队列,异步执行
- 开多个线程,异步执行
- 开多个线程,异步执行,每次开启多少个线程是不固定的(线程数,不由我们控制),线程数是由gcd来决定的
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
- 并行队列,同步执行
- 不开线程,顺序执行
//1 并行队列,同步执行 ----- 串行队列,同步执行 不开线程,顺序执行
- (void)demo1 {
//并行队列
dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"hello %d %@",i,[NSThread currentThread]);
});
}
}
//2 并行队列,异步执行 开多个线程,无序执行
- (void)demo2 {
//并行队列
dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"hello %d %@",i,[NSThread currentThread]);
});
}
}
六、主队列
- 主队列,异步任务
- 不开线程,同步执行
- 主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后在执行任务
- 主队列又叫 全局串行队列
- 主队列,同步执行
- 程序执行不出来(死锁)
- 死锁的原因,当程序执行到下面这段代码的时候
- 主队列:如果主线程正在执行代码,就不调度任务
- 同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)
dispatch_sync(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
七、 主队列和串行队列
- 主队列和串行队列的区别
- 串行队列:必须等待一个任务执行完成,再调度另一个任务
- 主队列:以先进先出调度任务,如果主线程上有代码在执行,主队列不会调度任务
- (主队列,同步执行)放入异步执行 解决死锁
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"全局队列,异步执行 %@",[NSThread currentThread]);
//此时这行代码 在子线程中运行,同步执行不用等待主线程执行此同步执行的任务
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"主队列,同步执行 %@",[NSThread currentThread]);
});
NSLog(@"==");
});
//1 主队列,异步执行 主线程,顺序执行
//主队列的特点:先执行完主线程上的代码,才会执行主队列中的任务
- (void)demo1 {
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"hello %d %@",i,[NSThread currentThread]);
});
}
}
//2 主队列,同步执行 --- 主线程上执行才会死锁
//同步执行:会等着第一个任务执行完成,才会继续往后执行
- (void)demo2 {
NSLog(@"begin");
for (int i = 0; i < 10; i++) {
//死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello %d %@",i,[NSThread currentThread]);
});
}
NSLog(@"end");
}
//3 解决死锁的问题
- (void)demo3 {
NSLog(@"begin");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
//死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello %d %@",i,[NSThread currentThread]);
});
}
});
NSLog(@"end");
}
八、全局队列
- 全局队列本质就是并发队列
dispatch_get_global_queue(0,0);//优先级 服务质量 一般都是 0 0
- 全局队列和并发队列的区别
- 并发队列有名称,可以跟踪错误,全局队列没有
- 在ARC中不需要考虑释放内存, dispatch_release(q);不允许调用。在MRC中需要手动释放内存,并发队列是create创建出来的 在MRC中见到create就要release,全局队列不需要release(只有一个)
- 一般我们使用全局队列
九、同步任务
- 同步任务的作用
- 平时生活中很多事情都需要先后进行,比如公司月底,会先(计算工资),然后才能(发工资)
- 在网络开发中,通常会把很多任务放在后台异步执行,有些任务会彼此"依赖"
- appStore 验证密码--扣费--下载应用
- 同步任务的特点:
- 队列调度多个异步任务前,指定一个同步任务,让所有的异步任务都等待同步任务完成,这就是所谓的”依赖”关系
- 演示代码
- 把验证密码的同步任务放到一个异步执行中去,因为验证密码比较耗时,此时验证密码这个同步任务也是在一个子线程中来完成的,不会阻塞UI
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//appStore下载应用--输入密码--扣费--下载应用
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"输入密码 %@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"扣费 %@",[NSThread currentThread]);
// [NSThread sleepForTimeInterval:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"下载应用 %@",[NSThread currentThread]);
});
});
}
各种队列的执行效果
十、Barrier阻塞
- 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新
- 适合于大规模的 I/O 操作
- 当访问数据库或文件的时候,更新数据的时候不能和其他更新或读取的操作在同一时间执行,可以使用调度组不过有点复杂。可以使用dispatch_barrier_async解决。
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *photoList;
@end
@implementation ViewController
//懒加载
- (NSMutableArray *)photoList {
if (_photoList == nil) {
_photoList = [NSMutableArray array];
}
return _photoList;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
for (int i = 1; i<=1000; i++) {
[self downloadImage:i];
}
}
//模拟从网络上下载很多张图片,并且把下载完成的图片添加到mutableArray中
- (void)downloadImage:(int)index {
//并发队列
dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//模拟下载图片
NSString *fileName = [NSString stringWithFormat:@"%02d.jpg",index % 10 + 1];
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
UIImage *img = [UIImage imageWithContentsOfFile:path];
//等待队列中所有的任务执行完成,才会执行barrier中的代码
dispatch_barrier_async(queue, ^{
[self.photoList addObject:img];
NSLog(@"保存图片 %@ %@",fileName,[NSThread currentThread]);
});
// [self.photoList addObject:img];
NSLog(@"图片下载完成 %@ %@",fileName,[NSThread currentThread]);
});
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%zd",self.photoList.count);
}
@end
十一、GCD的其它操作
- 延迟操作
- 一次性执行
- 调度组
11.1 延时操作
延时操作
//延时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
• dispatch_after的定义
dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
• dispatch_after的参数
参数1 dispatch_time_t when
多少纳秒之后执行
参数2 dispatch_queue_t queue
任务添加到那个队列
参数3 dispatch_block_t block
要执行的任务
11.2 一次性执行
- 一次性执行是线程安全的
- 可以使用一次性执行创建单例对象,效率比互斥锁高
- (void)viewDidLoad {
[super viewDidLoad];
//1 延迟执行
// dispatch_time_t when, 延迟多长时间 精度到纳秒
// dispatch_queue_t queue, 队列
// dispatch_block_t block 任务
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//
// NSLog(@"task");
// });
//2 一次性执行
// for (int i = 0; i<20000; i++) {
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
// NSLog(@"hello %@",[NSThread currentThread]);
// });
// }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//当前线程上执行
//一次性执行的原理 ,判断静态的全局变量的值 默认是0,如果执行完成后,设置为-1
//once内部会判断变量的值,如果是0才执行
static dispatch_once_t onceToken;
NSLog(@"%zd",onceToken);
dispatch_once(&onceToken, ^{
NSLog(@"hello %@",[NSThread currentThread]);
});
NSLog(@"%zd",onceToken);
});
}
+ (instancetype)sharedNetworkTools {
static id instance = nil;
//线程同步,保证线程安全
@synchronized(self) {
if (instance == nil) {
instance = [[self alloc] init];
}
}
return instance;
}
+ (instancetype)sharedNetworkToolsOnce {
static id instance = nil;
//dispatch_once 线程安全的
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance == nil) {
instance = [[self alloc] init];
}
});
return instance;
}
//获取加锁,创建的时间
- (void)demo1 {
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int i=0; i<10000000; i++) {
[NetworkTools sharedNetworkTools];
}
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"加锁: %f",end - start);
}
//获取once,创建的时间
- (void)demo2 {
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int i=0; i<10000000; i++) {
[NetworkTools sharedNetworkToolsOnce];
}
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"once: %f",end - start);
}