深入浅出Runloop
书上得来终觉浅、绝知此事要躬行。本文参考来源:http://mrpeak.cn/blog/ios-runloop/
一.Runloop 简介
什么是runloop ,简单来说、就是一个 do while 死循环、每次loop 都会执行下面的事情,当没有任务的时候 就会进入休眠。
{
performmask() //执行任务
callout_to_observer() //通知外部
sleep() //休眠
}
Performtask()
每次loop都会都会执行若干个task,在我们runloop执行的任务又有哪些呢,总结下来有下面5种
- DoBlocks() //CFRunLoopPerformBlock
- DoSources0() //CFRunLoopSourceContext,CFRunLoopSourceCreate,CFRunLoopAddSource
- DoSources1() // source1 并不对开发者开放,系统会使用它来执行一些内部任务,比如渲染 UI
- DoTimers() //CFRunLoopDoTimers
- DoMainQueue() //_dispatch_main_queue_callback_4CF(msg)这一步执行与runloopModel 没有关系。
callout_to_observer
主要是来通知外部观察者当前runloop执行了那些任务,当前runloop处于那种状态。有如下三种方式 以及相对应调用的函数。
- DoObserver-Timer
通过如下函数调用
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
- DoObserver-Source0
通过如下函数调用
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
这是上述五种执行任务方式中 ,两种可以注册 observer 的,其他几个都不支持,mainQueue,source1,block 都不行。所以理论上,是没有办法准确测量各个任务执行的时长的。
- DoObserver-Activity
DoObserver-Activity是runloop 用来通知外部自己当前处于那个状态。根据源码runloop有如下几种状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//开始进入某个Model
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),//开始休眠之前
kCFRunLoopAfterWaiting = (1UL << 6),//休眠中恢复
kCFRunLoopExit = (1UL << 7),//退出切换model
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
sleep()
没有任务就休眠,有任务就执行。
Runloop 完整执行流程
根据RunLoop简介 我们已经将RunLoop 细分为三类,但是RunLoop并非是按上述划分顺序执行的,而他们是相互交错运行的。

根据上面的流程图 呈现了 五种任务执行和六种通知外部函数的执行过程。
RunLoop Model
RunLoopModel 非常重要,核心点。
在一个程序运行中有一个主线程的RunLoop 和很多个子线程的Runloop。主线程的runLoop默认是开启状态,在我们的RunLoop里面又存在多个RunLoopModel,每个Model 都在执行上面描述的过程。在上面的任务和外部通知中大部分函数是和我们Model进行绑定执行的。RunLoop的结构源码如下
struct __CFRunLoopMode {
...
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
CFIndex _observerMask;
...
};
RunLoopModel 的种类分为common和private两大类共五种。
公共的
- kCFRunLoopDefaultMode //App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode //界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- kCFRunLoopCommonModes //集合模式 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
私有的
- UIInitializationRunLoopMode //在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
- GSEventReceiveRunLoopMode //接受系统事件的内部 Mode,通常用不到
我们常用的也就是公共里面的两种 kCFRunLoopDefaultMode 和 UITrackingRunLoopMode
RunLoop的作用
- 开发者通过RunLoop执行自己添加的任务
- 分析整个项目主线程的运行情况、检测卡顿等问题。
RunLoop和线程的关系
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
- RunLoop在第一次获取时创建,在线程结束时销毁