RunLoop
- 概念
- 数据结构
- 事件循环机制
- RunLoop与NSTimer
- RunLoop与多线程
什么是RunLoop?
-
是通过内部维护的事件循环来对事件/消息进行管理的一个对象
- 没有消息需要处理的时候,休眠以避免资源占用
- 有消息需要处理时,立刻被唤醒
没有消息需要处理时,休眠以避免资源占用
用户态->内核态
- 有消息需要处理时,立刻被唤醒
内核态->用户态
用户态、内核态
用户态就是用开发使用的API都是用户态,需要用到系统内核的时候就是用到内核态,
每一个用户,也就是App都能让手机开关机,这种行为是不被允许的,所有才有一个用户态到内核态的切换,
什么是事件循环?
维护的事件循环可以让我们不断的处理消息和事件,对他们进行管理,当没有消息进行处理的时候,会发生一些用户态到内核态的切换。由此来进行当前线程的休眠,同时会发生内核态到用户态的切换,然后当前用户线程会被唤醒。
我们的main函数为什么能保持不退出?
在我们的main函数中会调用UIApplicationMain这个函数,这个函数里面做了一个运行循环(RunLoop),不断的接收事件,滑动列表处理事件的返回,处理消息完成以后会继续等待,这个循环不是简单的for循环而是从用户态到内核态的切换(休眠),从内核态到用户态的切换(唤醒)。
RunLoop的数据结构
NSRunLoop是对CFRunLoop的封装,提供了面向对象的API。
CFRunLoop的代码是开源的,可以通过这个开源看到CFRunLoop的数据结构
- CFRunLoop
- pthread ->代表线程 一对一
- currentMode CFRunLoopMode的数据结构
- modes MutableSet<CFRunLoopMode>
- commonModes MutableSet<NSString>
- commonModeItems MutableSet<Observers/Timers/Sources>
- CFRunLoopMode
- name mode的名字,通过名称来查找是哪一个mode
- sources0 MutableSet
- sources1 MutableSet
- observers
- timers
- Source/Timer/Observer
CFRunLoopSource
- sources0
- 需要手动唤醒线程
- sources1
- 具备唤醒线程的能力
CFRunLoopTimer
基于事件的定时器,和NSTimer和toll-free bridged
CFRunLoopObserver
观测时间点:
- kCFRunLoopEntry 准备启动的时候系统会给一个回调
- kCFRunLoopBeforeTimers runloop将要对timer一些事件进行处理
- kCFRunLoopBeforeSources 将要处理一些source事件
- kCFRunLoopBeforeWaiting 将要进入休眠状态,即将发生用户态到内核态的一个转换
- kCFRunLoopAfterWaiting 内核态切换到用户态不久
- kCFRunLoopExit RunLoop退出
各个数据结构之间的关系
- RunLoop和线程是一一对应的
- 一个RunLoop可以拥有多个Mode
- 一个mode可以有多个source、Timer、Observer
Runloop的Mode
mode跟mode中的事件是不会相互影响的,当我把事件添加到mode1上面运行的时候在mode2上面那么mode1的事件永远不会执行,mode和mode之间是孤立的。
我在tableview滚动的时候,你的banner控件不会自动切换,这就是RunLoop的mode的一种形式。
NSRunLoopCommonModes
- 并不是实际存在的模式
- 是同步source/timer/Observer到多个mode的一种技术方案。
NSRunLoop、CFRunLoop的Run方法
void CFRunLoopRun()
RunLoop启动的流程
- 1、即将进入Runloop
- 2、将处理Timer/source0事件
- 3、处理Source0事件
- 4、如果有source1要处理 跳转到8
- 5、线程将要休眠
- 6、休眠,等待唤醒
- source1可以唤醒
- Timer事件可以唤醒
- 外部手动唤醒
- 7、线程刚被唤醒
- 8、处理唤醒时收到的消息 这一步会直接跳转到2中
RunLoop的核心
- 用户态到内核态 休眠
- 内核态到用户态 唤醒
滑动TableView的时候我们的定时器还会生效吗?
当运行的Timer是用默认start运行的timer,那么timer的RunLoop的mode是defaultMode,当用户滑动TableView的时候,当前运行的RunLoop的mode会切换到tracking这个mode,这就造成了Timer运行事件的隔离,最终导致Timer事件在华东过程中,没有任何反应。解决方式是,把timer添加到commonmodes这个runloop中。commonmodes并不是一个实际的runloopmode,他只是把一些mode打上commonmode标记,然后可以把某一个是事件源,比如说是timer同步到多个mode当中。
RunLoop与多线程
- 线程和RunLoop一一对应的
- 自己创建的线程默认是没有RunLoop的
怎么实现一个常驻的线程
- 为当前线程开启一个Runloop (获取当前RunLoop,如果当前没有,则会自动创建)
- 为该RunLoop中添加一个Port/source等维持RunLoop
- 如果一个RunLoop中间没有item(Port或者Source)资源的话,是维持不住RunLoop的,会直接退出
- 启动该RunLoop
代码如下
// 创建一个Source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 创建RunLoop,同时向RunLoop的DefaultMode下面添加Source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 如果可以运行
while (runAlways) {
@autoreleasepool {
// 令当前RunLoop运行在DefaultMode下面
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
// 某一时机 静态变量runAlways = NO时 可以保证跳出RunLoop,线程退出
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
面试题
什么是RunLoop,他是怎么做到有事做事,没事休息
- 是通过内部事件循环来对事件和消息进行管理的一个对象
- 这个事件循环,不是简单的for循环,而是有事做事,没事休眠的一个循环,避免浪费系统资源
- RunLoop的休眠和唤醒是系统对于用户态和内核态进行切换造成的
RunLoop与线程是怎么的关系?
RunLopp和线程是一一对应的。
新创建的线程模式是没有RunLoop的
如何实现一个常驻线程?
- 创建一个线程对应的RunLoop
- 向这个RunLoop中添加一个Source或者timer、observer、或者port
- 最后调用CFRunLoopRunInMode
运行的模式和添加的的模式应该属于一个mode这样才能实现常驻线程
怎么保证子线程数据回来更新UI不打断用户滑动操作?
用户滑动操作实际上是在RunLoop中的trackingMode下,只要我们保证子线程的数据更新UI的时候处于defalutmode下,就不会影响用户动画操作。