RunLoop
首先要了解RunLoop是什么?一些名词解析:
- RunLoop:泛指运行循环,是持续循环来实现一些事情。
- runLoop对象:是runloopref的封装,是一个对象。运行循环就是在这个对象创建后开始做的。
- loop:运行循环的代码实体,本质是一个包含do while语句的方法,loop其实就是runloop对象中的一个循环方法。
- commonModeItems:这是一个信号数据,记录触发事件的响应信号。包括:sources、Timer、observer。
- sources0:app内的触摸事件、performSelector方法,app内部自己发起并处理。
- sources1:由RunLoop和内核管理,由mach_Port驱动。
- mach_port:端口,可以理解为苹果设备接收到的硬件或者应用发送过来的信号。是一个信号入口。
- Timer:时间触发器。
- Observer:观察者,runloop的状态切换会调用,可以通过监听Observer来获取runloop的状态切换。
- dispatch:GCD的方法,如:dispatch_async这些方法
- Mode:是一个模式,用于区分到底需要处理哪些事件。两个模式间的切换需要退出loop,因为mode是loop方法的一个入参,要改变mode只有重新执行的时候传新的mode。两个mode互不影响。mode有非常多种,但是在iOS中常用的有两种,这两个 Mode 都已经被标记为”Common”属性
kCFRunLoopDefaultMode:一种是平时模式。
UITrackingRunLoopMode:一种是滚动模式。
kCFRunLoopCommonModes :注意这个是一个特殊的标记。他不属于两个模式中的任何一个,但是如果一个信号被标记为这个特殊标记,那么他将会被加入到顶层的commonModeItems
中,这样在标记了Common
的mode的loop中,就会查找响应commonModeItems的信号。(这也是为什么设置了这个模式的Timer可以在滚动的时候也响应计时的原因) - runloop状态:是当前loop处于什么状态,标识它正在走哪里的代码,runloop状态有6个。
kCFRunLoopEntry:进入loop,在调用
__CFRunLoopRun
函数前,会被标记为这个状态。
kCFRunLoopBeforeTimers:触发 Timer 回调,判断是有Timer触发的话会走Timer回调,走回调前会被标记为这个状态。
kCFRunLoopBeforeSources:触发 Source0 回调,判断是Source0触发的话会走Source0回调,走回调前会被标记为这个状态。
kCFRunLoopBeforeWaiting:将要进入休眠。等待 mach_port 消息,在处理完事件后,会标记为这个状态,然后进入到内部的do while循环中等待激活。
kCFRunLoopAfterWaiting:从休眠中唤醒。 接收 mach_port 消息,已经被激活,在处理事件前会标记为这个状态。然后开始处理事件。
kCFRunLoopExit:退出 loop
runloop的工作:
线程的消息事件是依赖于runloop的,runloop通过监听输入源来进行调度处理。输入源包括两种:一种是来自另一个线程或者来自不同应用的异步消息;另一种是来自预订时间或者重复间隔的同步事件。
RunLoop对象中的属性结构如下图(网上借鉴的):
runloop的逻辑流程如下图(网上借鉴的):
runloop在开发中的运用
UI卡顿的监控:
了解了runloop的工作原理,可以知道,如果loop处理一个输入源事件的时间过长,就没有时间处理下一个输入源的事件。我们一般所了解的卡顿都是UI界面上滑动的卡顿,所以我们监控UI的卡顿,就是监控loop在一段时间是否完成了输入源时间的处理。
- 简单来说说,UI卡顿监控的方法就是runloop状态切换的监控。如果状态长时间没有切换,又是主线runloop的时候,就会出现UI卡顿。
- 监控状态的选择,进入和退出loop属于内部处理,不会花费太多的时间,所以可以不监控;kCFRunLoopBeforeWaiting是进入休眠等待唤醒,这个状态runloop可以随时被唤醒,也可以切换到其他mode处理事件,所以也不需要监控。那么我们真正要注意的状态就只剩下三个:
kCFRunLoopBeforeTimers,这个状态不太重要,是时间触发器的回调,所以我们可以不监听这个状态到下一个状态的切换时间。
kCFRunLoopBeforeSources,在这个状态后,会走__CFRunLoopDoBlocks
或者跳到handle_msg
继续处理各种输入源的事件,所以,这个状态下会走大部分的事件处理。这个状态的切换也就变成卡顿监控的一个重要状态
。
kCFRunLoopAfterWaiting,唤醒后,走的代码逻辑就是handle_msg
的代码,所以这个时候也是处理大部分输入源的事件,所以这个状态的切换也是卡顿监控的一个重要状态
。
实际的处理耗时可以简化为这样:
滚动界面的倒计时:
在scrollview中添加Timer时,需要把mode设置为kCFRunLoopCommonModes,这样在平常和滚动的状态,timer都可以触发,因为timer是加到了两个mode中。
参考:
深入理解RunLoop