【iOS】RunLoop底层详解

1、什么是Runloop

Runloop是通过内部维护的事件循环来对事件/消息进行管理的一个对象。事件循环不是while死循环,而是状态转换,即用户态-内核态的转换。

image.png

2、RunLoop事件循环

下图是仿照Runloop内部逻辑实现的伪代码:

image.png

根据以上代码我们可以看出,RunLoop主要处理以下6类事件:

1.Observer事件:runloop中状态变化时进行通知。(微信卡顿监控就是利用这个事件通知来记录下最近一次main runloop活动时间,在另一个check线程中用定时器检测当前时间距离最后一次活动时间过久来判断在主线程中的处理逻辑耗时和卡主线程)。

2.Block事件:非延迟的performSelector,非延迟的dispatch_after,block回调。

3.Main_Dispatch_Queue事件:GCD中dispatch到main queue的block会被dispatch到main loop执行。

4.Timer事件:延迟的performSelector,延迟的dispatch_after,timer事件等。

5.Source0事件:非基于Port的处理事件,不能主动唤醒休眠中的RunLoop,需要手动触发。

6.Source1事件:基于mach_Port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的RunLoop(推测CADisplayLink也是这里触发)。

简化的流程图如下:

1877784-94c6cdb3a7864593.png

在每次运行开启RunLoop的时候,所在线程的RunLoop会自动处理之前未处理的事件,并且通知相关的观察者。具体的顺序如下:

  1. 通知观察者RunLoop已经启动

  2. 通知观察者即将要开始的定时器

  3. 通知观察者任何即将启动的非基于端口的源

  4. 启动任何准备好的非基于端口的源

  5. 如果基于端口的源准备好并处于等待状态,立即启动,并进入步骤9

  6. 通知观察者线程进入休眠状态

  7. 将线程置于休眠直到任一下面的事件发生:

    • 某一事件到达基于端口的源

    • 定时器启动

    • RunLoop设置的时间已经超时

    • RunLoop被显式唤醒

  8. 通知观察者线程将被唤醒

  9. 处理未处理的事件

    • 如果用户定义的定时器启动,处理定时器事件并重启RunLoop,进入步骤2

    • 如果输入源启动,传递相应的消息

    • 如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2

  10. 通知观察者RunLoop结束。

3、RunLoop和线程的关系

RunLoop 和线程是息息相关的,我们知道线程的作用是用来执行特定的一个或多个任务,在默认情况下,线程执行完之后就会退出,就不能再执行任务了。这时我们就需要采用一种方式来让线程能够不断地处理任务,并不退出。所以,我们就有了 RunLoop。

1、一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的 RunLoop 对象。

2、RunLoop 并不保证线程安全。我们只能在当前线程内部操作当前线程的 RunLoop 对象,而不能在当前线程内部去操作其他线程的 RunLoop 对象方法。

3、RunLoop 对象在第一次获取 RunLoop 时创建,销毁则是在线程结束的时候。

4、主线程的 RunLoop 对象系统自动帮助我们创建好了,而子线程的 RunLoop对象需要我们主动创建和维护。

4、RunLoop与硬件事件的关系

当一个硬件事件(触摸/锁屏/摇晃/加速等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收, 随后由mach port 转发给需要的App进程。
苹果注册了一个 Source1 (基于 mach port 的) 来接收系统事件,通过回调函数触发Source0(所以UIEvent实际上是基于Source0的),调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。

如果上一步的_UIApplicationHandleEventQueue() 识别到是一个guesture手势,会调用Cancel方法将当前的touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,其回调函数为 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

5、RunLoop与Core Animation渲染的关系

iOS 图形服务接收到 VSync 信号后,会通过 IPC 通知到 App 内。App 的 Runloop 在启动后会注册对应的 CFRunLoopSource 通过 mach_port 接收传过来的时钟信号通知,随后 Source 的回调会驱动整个 App 的动画与显示。Core Animation 在 RunLoop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件。这个 Observer 的优先级是 2000000,低于常见的其他 Observer。

当UI改变( Frame变化、 UIView/CALayer 的层级结构变化等)时,或手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay 方法后,这些操作最终都会被 CALayer 捕获,并通过 CATransaction 提交到一个中间状态去。

当RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知。这时 CA 注册的那个 Observer 就会在回调中,把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,CA 会通过 DisplayLink 等机制多次触发相关流程。

6、RunLoop与AutoReleasePool的关系

在iOS应用启动后会注册两个Observer管理和维护AutoreleasePool。在应用程序刚刚启动时打印currentRunLoop可以看到系统默认注册了很多个Observer,其中有两个Observer的callout都是_ wrapRunLoopWithAutoreleasePoolHandler,这两个是和自动释放池相关的两个监听。

第一个Observer会监听RunLoop的进入,它会回调objc_autoreleasePoolPush()向当前的AutoreleasePoolPage增加一个哨兵对象标志创建自动释放池。这个Observer的order是-2147483647优先级最高,确保发生在所有回调操作之前。

第二个Observer会监听RunLoop的进入休眠和即将退出RunLoop两种状态,在即将进入休眠时会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush()根据情况从最新加入的对象一直往前清理直到遇到哨兵对象。而在即将退出RunLoop时会调用objc_autoreleasePoolPop() 释放自动自动释放池内对象。这个Observer的order是2147483647,优先级最低,确保发生在所有回调操作之后。

主线程的其他操作通常均在这个AutoreleasePool之内(main函数中),以尽可能减少内存维护操作。当然你如果需要显式释放(例如循环)时可以自己创建AutoreleasePool,否则一般不需要自己创建。

你可能感兴趣的:(【iOS】RunLoop底层详解)