底层原理-33-runloop

1,628 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

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];

底层对应的实现:

image.png

  • 查看_CFRunLoopGet0

image.png 根据线程作为key获取字典中的runloop,通过key-value的形式页可以说明线程和runloop是一一对应的关系,如果没有获取到就创建一个新的runloop,存放到字典中。runloop分为2种,一种是主线程的,一种是其它线程的。

2.2 Runloop的创建

  • 查看__CFRunLoopCreate

image.png 主要初始化runloop,并设置其相关的属性,继续查看CFRunLoopRef

image.png 一个runloop包含多个model和item,即一个Mode对应多个Item,而一个item中,包含了timer、source、observer,如下所示

image.png

  • NSRunLoopMode 我们创建往runloop添加事件的时候会添加model,苹果一般供我们使用的使用2种

image.png RunLoopCommonModes是一个集合包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode

image.png

  • NSDefaultRunLoopMode默认的mode,正常情况下都是在这个mode

  • NSConnectionReplyMode:Cocoa与NSConnection对象一起使用此模式来监控回复。您很少需要自己使用此模式。

  • NSModalPanelRunLoopMode:识别用于模态面板的事件

  • NSEventTrackingRunLoopMode:Cocoa使用此模式限制鼠标拖动循环和其他类型的用户界面跟踪循环期间的传入事件

  • NSRunLoopCommonModes:伪模式,这是一组可配置的常用模式,灵活性更好 我们代码运行下

image.png 在主线程中当前的model只有一种是默认kCFRunLoopDefaultMode,但是包含多种model。

  • item输入源 我们之前分析model可以包含多个item 创建输入源时,您将其分配给运行循环的一个或多个模式。模式会影响在任何给定时刻监控哪些输入源。大多数时候,您在默认模式下运行运行运行循环,但您也可以指定自定义模式。如果输入源不处于当前监控模式,则将其生成的任何事件将保留,直到运行循环以正确的模式运行 image.png

  • Source表示可以唤醒RunLoop的一些事件,例如用户点击了屏幕,就会创建一个RunLoop,主要分为Source0Source1

    • Source0 表示 非系统事件,即用户自定义的事件
    • Source1 表示系统事件,主要负责底层的通讯,具备唤醒能力
  • Timer 就是常用NSTimer定时器这一类

  • Observer 主要用于监听RunLoop的状态变化,并作出一定响应,主要有以下一些状态

image.png

  • 运行回路的入口。
  • 当运行循环即将处理计时器时。
  • 当运行循环即将处理输入源时。
  • 当运行循环即将进入睡眠状态时。
  • 当运行循环唤醒时,但在它处理唤醒它的事件之前。
  • 运行循环的出口。 我们添加定时器运行,block执行处打印堆栈信息

image.png 查看__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

image.png

  • 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__

2.3 RunLoop执行

runloop执行通常不是在主线程的话需要手动开启,主线程是默认开启的。在子线程中我们实现[[NSRunLoop currentRunLoop] run],查看底层实现

image.png

  • 进入__CFRunLoopRun源码,针对不同的对象,有不同的处理

    • 如果有observer,则调用 __CFRunLoopDoObservers
    • 如果有block,则调用__CFRunLoopDoBlocks
    • 如果有timer,则调用 __CFRunLoopDoTimers
    • 如果是source0,则调用__CFRunLoopDoSources0
    • 如果是source1,则调用__CFRunLoopDoSource1

我们查看timer

image.png 继续查看__CFRunLoopDoTimers

image.png 主要进行runloop中所有timer进行处理,以及处理后的回调调用
继续查看__CFRunLoopDoTimer

image.png 和我们之前打印堆栈一样

image.png

  • timer的大概流程为:
  1. 为timer设置model,添加进入线程中的runloop中。
  2. 如果不是主线程,子线程则需要手动开启[[NSRunLoop currentRunLoop] run] 运行runloop
  3. 在__CFRunLoopRun中判断source是timer则执行__CFRunLoopDoTimers,执行所有的runloop中所有的timer。
  4. 单个timer中会执行__CFRunLoopDoTimer,对单个timer进行处理,执行完成后执行对应的回调函数。 下图是苹果官网对于runloop的描述苹果官方文档

image.png 至于其他几种source类似,有兴趣的小伙伴可以去官网看看。

2.4 runloop的底层原理

从之前的堆栈信息可知CFRunLoopRun->CFRunLoopRunSpecific-> __CFRunLoopRun
进入CFRunLoopRun源码,其中传入的参数1.0e10(科学计数) 等于 1* e^10,用于表示超时时间

image.png

  • CFRunLoopRunSpecific

image.png 首先根据modeName找到对应的mode,当前的runloop 分为几种状态

image.png 我们关注的是:

  1. 如果是entry,则通知observer,即将进入runloop
  2. 如果是exit,则通过observer,即将退出runloop
  3. 如果是其他中间状态,主要是通过runloop处理各种源
  • 查看__CFRunLoopRun源码

image.png 大致流程如下所视:

image.png