Runloop
Runloop作用:
- 保持程序的持续运行
- 处理程序的各种事件(触摸事件、定时器事件等)
- 节约CPU资源,提高程序性能
程序中Main函数中的UIApplicationMain函数主要作用就是创建了一个主运行循环
主线程几乎所有的事情都是交给了runloop去完成,比如UI界面的刷新,点击事件的处理,performSelector
等等,但并非所有的任务都是由runloop完成
Runloop有两种形式:
- OC语言-- NSRunloop
- C语言 -- CFRunloopRef
Runloop与线程的关系:
- 每一条线程都有一个与之对应的runloop对象
- runloop保存在全局的字典内,是以线程为key,runloop为value
- 线程刚创建时没有runloop对象的,runloop会在第一次获取他的时候创建
- runloop在线程销毁时销毁
Runloop与Mode的关系:
Runloop只能选择其中一个mode,称之为CurrentMode
如果需要切换mode,只能退出当前mode,重新选择一个Mode进入
不同组的source0,source1,observers,timer能分隔开,互不影响
注:如果mode里没有任何source0等等的事件,则runloop会立马退出
切换mode也是在Runloop内部,不会导致程序退出
常见的2种Mode:
- kCFRunloopDefaultMode //程序的默认Mode
- UITrackingRunloopMode //界面追踪Mode
注:kCFRunloopCommonModes默认包括了kCFRunloopDefaultMode和UITrackingRunloopMode
kCFRunloopCommonModes并不是一个真正的模式,只是一个标记,而kCFRunloopDefaultMode和UITrackingRunloopMode的模式是真正意义的模式,timer在设置了kCFRunloopCommonModes标记的模式下都能运行,而kCFRunloopDefaultMode和UITrackingRunloopMode这两个模式都是kCFRunloopCommonModes标记的,
使用NSRunloop方式和CFRunloopRef的方式获取两者,会发现地址不一致,实际是NSRunloop是对CFRunloopRef的一层封装,其本质地址还是CFRunLoopRef的地址
注:(lldb)bt可以获取函数调用栈
Mode中各个成员的含义:
source0:
1.触摸事件
2.performSelector :OnThread
source1
1.基于Port的线程间通信
2.系统事件的捕捉Timer
1.NSTimer
2.performSelector :afterDelay
//这句代码的本质是往Runloop中添加定时器Observers
1.监听runloop的状态
2.UI刷新(在runloop休眠之前)
3.自动释放池(在runloop休眠之前)
Runloop运行逻辑:
Runloop处理事件一般都是调用_CFRUNLOOP_IS_CALLING_OUT_TO_XXXX
函数进行事件处理
GCD中当仅在子线程做事情时,是不依赖Runloop的,但当需要回到主线程等线程间操作的时候,需要runloop来执行
线程休眠其实和线程阻塞类似,但和通过代码例如while(1)令线程阻塞是不一样的,使用代码while(1),线程并未休眠,而是不断进行while(1)判断,汇编也并未停止,而线程休眠是可以真正意义上做到令线程不做任何的事情
Runloop休眠的实现原理: 会从用户态切换到内核的内核态,在内核态执行休眠的目的,是可以实现真正意义上的休眠(即没有消息就是休眠,有消息就唤醒,并回到用户态)
Runloop响应用户操作具体流程:先通过source1事件进行捕捉,然后再source0事件进行处理
Runloop在实际开发中的应用:
- 线程保活
- 解决NSTimer在滑动中停止工作的问题
- 监控应用卡顿
- 性能优化
- 解决NSTimer在滑动中停止工作的问题:
timer是在默认模式下工作的,所以在滑动中(uitracking)的模式下是会停止工作的
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1
repeats:YES
block:^(NSTimer * _Nonnull timer) {
}
//带有schedule的是会将定时器直接添加到默认的模式
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
}]
//该方法不会添加到默认模式去,只会单纯创建一个timer
//后续需要手动调用runloop的addtimer方法才能令定时器工作
- 线程保活
Runloop的run方法是一个无线的循环,故外部停止runloop只是停止其中一次循环,故是无法停止Runloop的,run方法用于开启一个永不销毁的线程
使用runMode方法在唤醒后,做完对应事情,runloop就会退出
performSelector方法的waitUntilDone设置为YES,代表该语句执行完后才会执行后面的语句,设置为NO,则后面语句与该语句是一起执行的
GCD只负责创建线程,不负责线程保活
一个问题:
Q:保住线程的命为什么要用Runloop,用强指针不就好了?
A:准确的说,使用Runloop是为了让线程保持激活状态,线程的任务一旦执行完毕,生命周期就结束,无法再使用