1. RunLoop介绍
- Runloop被称为消息循环或事件循环
- 每个线程里,都有一个消息循环
- 默认情况下,主线程开启消息循环,子线程不开启
目的
- 保证程序不退出
- 负责处理输入事件
- 如果没有事件处理,会让程序进行休眠
消息类型(事件类型)
- Input Sources(输入源)
Input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects.
包括键盘鼠标事件,NSPort,NSConnection,等。
- Timer Sources(定时源)
An NSRunLoop object also processes NSTimer events.
就是定时器NSTimer
常用的循环模式
- 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.
如何使用
- 创建消息
- 把消息加入到消息循环中,并指定循环的模式
加入的2种方法
[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
只有当消息的循环模式和当前线程中消息循环的模式像符合,消息才能被运行
子线程中的消息循环
子线程中默认不开启消息循环
开启方式
-
Run方法
[[NSRunLoop currentRunLoop] run];
缺点:开启之后无法关闭,并且在他之后的代码都不会被运行,因为他是一个死循环
-
runUntilDate
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
- 苹果推荐方法
BOOL shouldKeepRunning = YES; // global NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
问题演示
- 线程没有执行方法,线程立马销毁
NSThread *thread = [[NSThread alloc] init];
[thread start];
[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];
- 线程指定方法,但是没有开启消息循环
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
[thread start];
[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];
..........................
- (void)demo {
NSLog(@"over");
}
2. GCD简介
- 全称是Grand Central Dispatch
- 纯C语言,提供了非常多强大的函数
优点
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
核心组成部分
任务&队列
任务:要干什么事情
队列:用来存放任务
如何做
- 确定要做的事情
- 把任务加到队列里
- 没了
GCD会自动将任务从队列里面取出,放到对应的贤臣各种去执行
任务取出的原则是先进先出FIFO(First in first out)
举例
//任务
dispatch_block_t block;
//队列
dispatch_queue_t queue;
//把任务放到队列里
dispatch_async(queue, block);
3. 队列执行任务的方式
同步执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
异步执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
区别
同步:在当前线程上执行 异步:在其他线程上执行
4. 队列的种类
并发队列(Concurrent Dispatch Queue)
- 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
- 并发功能只有在异步(dispatch_async)函数下才有效
dispatch_queue_t queue = dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
串行队列(Serial Dispatch Queue)
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
dispatch_queue_t queue = dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);
5. 同步、异步、并发、串行的基本概念
同步和异步决定了要不要开启新的线程
- 同步:在
当前
线程中执行任务,不具备
开启新线程的能力 - 异步:在
新的
线程中执行任务,具备
开启新线程的能力
并发和串行决定了任务的执行方式
- 并发:多个任务并发
(同时)
执行 - 串行:一个任务执行完毕后,再执行
下一个
任务
6. 串行队列的执行
串行队列的同步执行
- 不开线程,同步执行(在当前线程执行)
#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);
});
}
7. 并行队列的执行
并行队列,异步执行
- 开多个线程,异步执行,每次开启多少个线程是
不固定
的(线程数,不由我们控制),线程数是由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);
});
}
并行队列,同步执行
- 不开线程,顺序执行,与
串行队列的同步执行
一模一样
8. 系统提供的队列
iOS已经为我们准备好了2个常用队列主队列
列和全局并发队列
,开发中用的很多
主队列
- 主队列是负责在主线程调度任务的
- 会随着程序启动一起创建
- 主队列只需要获取不用创建
dispatch_queue_t queue = dispatch_get_main_queue();
主队列,异步任务
- 不开线程,同步执行
- 主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后在执行任务
- 主队列又叫
全局串行队列
主队列,同步执行
- 程序执行不出来
(死锁)
死锁的原因
当程序执行到下面这段代码的时候
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
主队列:如果主线程正在执行代码,就不调度任务
同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)
全局并发队列
- 为了方便程序员开发提供的队列,与并发队列行为
相同
9. 同步异步串行并发的组合结果
全局并发队列 | 手动创建串行队列 | 主队列 | |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁 |
异步(async) | 有开启新线程,并行执行任务 | 开启新线程,并行执行任务 | 没有开启新线程 ,串行执行任务 |
10. GCD延时函数使用
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
要执行的任务
例子
//延时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
11. GCD一次执行和单例实现
有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”
// MARK: 一次性执行
- (void)once {
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"一次性吗?");
});
NSLog(@"come here");
}
- dispatch_once是
线程安全
的,因为内部也有一把锁,苹果推荐使用该种方式来实现单例模式
测试
单例的2中实现
- 使用 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;
}
- 对比结果
- (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);
}
12. GCD任务组
使用场景:在组里的异步任务都执行完毕后,再去执行其他操作,例如下载歌曲,等所有的歌曲都下载完毕之后 转到 主线程提示用户
- 方式一:dispatch_group_async常用用法
/**
调度组-在一组异步代码执行完毕后,统一获得通知
应用场景:将一组图像异步缓存到本地之后统一获得通知!
*/
- (void)group1 {
// 队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 调度组
dispatch_group_t group = dispatch_group_create();
// 添加异步
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download A %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"download B %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"download C %@", [NSThread currentThread]);
});
// 调度组通知 - 监听群组中,所有异步执行的代码完成后,得到通知
// 异步监听!
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"下载完成 %@", [NSThread currentThread]);
});
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]);
}
注意:
不要跟dispatch_barrier_async混淆,一个是所有任务都完成后,一个是某个任务中的一段代码需要交给另一个线程有序的串行执行