小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1. Runloop的概念和作用
我们知道runloop
顾名思义就是运行循环,是事件接受和分发的一个机制
,是线程相关基础框架的一部分。一个runloop
就是一个事件处理
的循环
,用来不停调度工作
以及处理输入事件
。
Runloop
本质是一个while循环
,有任务就执行,没有就休息。和普通的while的循环不太一样,普通的while循环是是一种忙等状态
,会持续占用CPU
,而runloop
的while循环是闲等状态
,不会占用CPU资源。
RunLoop的作用
- 保持程序的
持续
运行 - 处理App中的各种
事件
(触摸、定时器、performSelector) 节省
cpu资源,提供程序的性能,该做事就做事,该休息就休息
2. Runloop源码分析
2.1 Runloop和线程的关系
runloop
我们知道是依赖于线程
中,在主线程中会自动开启
,子线程中需要我们手动添加。获取runloop的2种方式,主线程runloop和当前线程runloop
NSRunLoop *mainLoop = [NSRunLoop mainRunLoop];
NSRunLoop *currentLoop =[NSRunLoop currentRunLoop];
底层对应的实现:
- 查看
_CFRunLoopGet0
根据线程作为key
获取字典中的runloop
,通过key-value
的形式页可以说明线程和runloop是一一对应
的关系,如果没有获取到就创建
一个新的runloop
,存放到字典中。runloop分为2种,一种是主线程的,一种是其它线程的。
2.2 Runloop的创建
- 查看
__CFRunLoopCreate
主要初始化runloop,并设置其相关的属性,继续查看CFRunLoopRef
一个runloop包含多个model和item,即一个Mode对应多个Item
,而一个item中,包含了timer、source、observer,如下所示
NSRunLoopMode
我们创建往runloop添加事件的时候会添加model,苹果一般供我们使用的使用2种
RunLoopCommonModes是一个集合包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode
。
-
NSDefaultRunLoopMode
:默认
的mode,正常情况下都是在这个mode -
NSConnectionReplyMode
:Cocoa与NSConnection
对象一起使用此模式来监控回复。您很少需要自己使用此模式。 -
NSModalPanelRunLoopMode
:识别用于模态面板的事件 -
NSEventTrackingRunLoopMode
:Cocoa使用此模式限制鼠标拖动循环和其他类型的用户界面跟踪循环期间的传入事件 -
NSRunLoopCommonModes
:伪模式,这是一组可配置的常用模式,灵活性更好 我们代码运行下
在主线程中当前的model只有一种是默认kCFRunLoopDefaultMode,但是包含多种model。
-
item
输入源 我们之前分析model可以包含多个item 创建输入源时,您将其分配给运行循环的一个或多个模式。模式会影响在任何给定时刻监控哪些输入源。大多数时候,您在默认模式下运行运行运行循环,但您也可以指定自定义模式。如果输入源不处于当前监控模式,则将其生成的任何事件将保留,直到运行循环以正确的模式运行 -
Source
表示可以唤醒RunLoop的一些事件
,例如用户点击了屏幕,就会创建一个RunLoop,主要分为Source0
和Source1
Source0
表示 非系统事件,即用户自定义的事件Source1
表示系统事件,主要负责底层的通讯,具备唤醒能力
-
Timer
就是常用NSTimer
定时器这一类 -
Observer
主要用于监听RunLoop的状态变化,并作出一定响应,主要有以下一些状态
- 运行回路的入口。
- 当运行循环即将处理计时器时。
- 当运行循环即将处理输入源时。
- 当运行循环即将进入睡眠状态时。
- 当运行循环唤醒时,但在它处理唤醒它的事件之前。
- 运行循环的出口。 我们添加定时器运行,block执行处打印堆栈信息
查看__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
-
在
RunLoop
源码中查看Item
类型,有以下几种- block应用:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
- 调用timer:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- 响应source0:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- 响应source1:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
- GCD主队列:
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
- observer源:
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
- block应用:
2.3 RunLoop执行
runloop执行通常不是在主线程的话需要手动开启,主线程是默认开启的。在子线程中我们实现[[NSRunLoop currentRunLoop] run]
,查看底层实现
-
进入
__CFRunLoopRun
源码,针对不同的对象,有不同的处理- 如果有
observer
,则调用__CFRunLoopDoObservers
- 如果有
block
,则调用__CFRunLoopDoBlocks
- 如果有
timer
,则调用__CFRunLoopDoTimers
- 如果是
source0
,则调用__CFRunLoopDoSources0
- 如果是
source1
,则调用__CFRunLoopDoSource1
- 如果有
我们查看timer
继续查看__CFRunLoopDoTimers
主要进行runloop中所有timer进行处理,以及处理后的回调调用
继续查看__CFRunLoopDoTimer
和我们之前打印堆栈一样
- timer的大概流程为:
- 为timer设置model,添加进入线程中的runloop中。
- 如果不是主线程,子线程则需要手动开启[[NSRunLoop currentRunLoop] run] 运行runloop
- 在__CFRunLoopRun中判断source是timer则执行__CFRunLoopDoTimers,执行所有的runloop中所有的timer。
- 单个timer中会执行__CFRunLoopDoTimer,对单个timer进行处理,执行完成后执行对应的回调函数。 下图是苹果官网对于runloop的描述苹果官方文档
至于其他几种source类似,有兴趣的小伙伴可以去官网看看。
2.4 runloop的底层原理
从之前的堆栈信息可知CFRunLoopRun->CFRunLoopRunSpecific-> __CFRunLoopRun
进入CFRunLoopRun
源码,其中传入的参数1.0e10
(科学计数) 等于 1* e^10,用于表示超时时间
- CFRunLoopRunSpecific
首先根据modeName找到对应的mode,当前的runloop 分为几种状态
我们关注的是:
- 如果是
entry
,则通知observer,即将进入runloop
- 如果是
exit
,则通过observer,即将退出runloop
- 如果是其他中间状态,主要是通过
runloop
处理各种源
- 查看
__CFRunLoopRun
源码
大致流程如下所视: