关于runloop的个人理解

814 阅读3分钟

什么是runloop?

runloop通俗的来说,就是一个程序,从头到尾,一个调度任务和处理任务的事件循环。简单的说RunLoop是一种高级的循环机制,让程序持续运行,并处理程序中的各种事件,让线程在需要做事的时候忙起来,不需要的话就让线程休眠。

runloop的构成

runloop是由这几个类构成的: CFRunLoopRef CFRunLoopModeRef CFRunLoopSourceRef CFRunLoopTimerRef CFRunLoopObserverRef 其中,mode又包含了Source,Timer,Observer.一个runloop可以包含好几个mode,如下图

image.png

mode相关的

  • RunLoop在同一个时间只能且必须在一种特定的Mode下run
  • 更换Mode的时候,需要停止当前的Mode,然后重新启动Loop
  • Mode是iOS APP滑动顺畅的关键(在滑动的时候,处于当前滑动Mode,只能去run滑动相关的,所以,滑动的时候,就会很丝滑)
  • 可以定制自己的Mode(但是,基本是不可能的) mode的类型如下:
NSEventTrackingRunLoopMode:使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动 常见Mode
NSConnectionReplyMode
UIinitializationRunloopMode 私有的  APP启动时
NSRunLoopCommonModes 这是mode集合

Topic:NSEventTrackingRunLoopMode和NSTimer 当在一个有滑动页面的比如,有一个tableview页面的界面,加入了

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
                                    `

这个是直接加入到了NSDefaultRunLoopMode中的,那么如果在滑动tableview的时候,runloop会执行NSEventTrackingRunLoopMode这个mode,timer的mode,就不会执行,就出现了,滑动的时候,timer就停止了。如果不想timer被ScrollerView影响,就需要添加到NSRunLoopCommonModes中了。如下:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

以前只知道要这样做,但并不知道为什么要这么做。

source相关的

  • source0:处理APP内部事件,App自己负责管理(触发),如UIEvent(touch事件如果打断点,是可以知道是source0的,CFSocket(这是官方文档说的)
  • Source1:由Runloop和内核管理,Mach port驱动,如CFMachPort,CFMessagePort,与Runloop怎么被调起来,怎么去睡眠, 都与这个port有关,不过这方面的资料太少了。
  • 如有需要,可从中选择一种来实现自己的source。
  • 但是上一条基本不会发生😄

Observer相关的

主要是向报告runloop的状态

typedef CF_ OPTIONS (CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0) ,                       //即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1),       // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2) ,      //即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5) ,        // 即将进入休眠 ,
kCFRunLoopAfterWaiting = (1UL << 6) ,          / /刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),                       //即将退出Loop
kCFRunL oopAllActivities = 0x0FFFFFFFU       //所有状态集合
};```
这个东西其实很有作用,它不只是可以用debug,而且还能做一些事情,比如,在优化的时候,往往会面临时间和空间的优化,其实一般来说,我个人觉得很难让两者顾全,因为,优化了时间,必定增加CPU的压力,但是,在runloop中,我们可以把这方面的耗时操作,放置在runloop的休眠阶段,比如在kCFRunLoopBeforeWaiting中。

**说到这里,就有一个小的topic:Autorelease Pool缓冲池,是在哪里被释放掉了呢?**
![](media/16383689289345/16383726650147.jpg)

实际上,UIKit通过RunloopObserver在Runloop的两次sleep进行了**pop**和**push**,将这次loop中产生的Autorelease对象释放,我大胆猜测一下,认为是kCFRunLoopBeforeWaiting这个状态时候进行的。
## runloop的运行过程
流程

01、通知Observers:进入Loop 02、通知Observers:即将处理Timers 03、通知Observers:即将处理Sources 04、处理Blocks 05、处理Source0(可能会再次处理Blocks) 06、如果存在Source1,就跳转到第8步 07、通知Observers:开始休眠(等待消息唤醒) 08、通知Observers:结束休眠(被某个消息唤醒) 01> 处理Timer 02> 处理GCD Async To Main Queue 03> 处理Source1 09、处理Blocks 10、根据前面的执行结果,决定如何操作 01> 回到第02步 02> 退出Loop 11、通知Observers:退出Loop

网上有个流传很广的图
    
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e52481cb15a24c3b87d9c974a6ca689a~tplv-k3u1fbpfcp-watermark.image?)
我个人认为,这个图有个小错误,在第7步休眠的时候,已经被**port唤醒**,那么,应该是**source1**,而不是source0
# 个人总结
以前觉得Runloop真的晦涩难懂,但是现在沉下心来看了一下,有些地方还是晦涩难懂。在我的理解中,Runloop真的是贯穿着整个APP,不管是做什么的,底层基本上都会涉及到Runloop,以前觉得优化,是一把双刃剑,认为优化了时间必定会对资源大量损耗,从而会使内存方面有压力,优化内存,就需要牺牲时间,现在看来,还是想法还是有局限性。比如tableview的卡顿问题,短时间内去加载很多图片,以前想的是,监控滑动的时候,滑动的时候,就不加载渲染,滑动结束之后再加载渲染。其实看了Dunloop,就有一个更好的选择,把加载渲染图片放在NSRunLoopCommonModes的Runloop中。