RunLoop

作者: 某非著名程序员 | 来源:发表于2021-01-29 21:08 被阅读0次

1.源码阅读

1.1事件循环:do...while循环部分

RunLoop
  1. kCFRunLoopBeforeTimers:即将处理timer
  2. kCFRunLoopBeforeSources:即将处理source
  3. CFRunLoopDoSources0:处理source0事件
  4. CFRunLoopServiceMachPort:处理source1事件:读取msg_buffer,系统事件,如果有消息goto handle_msg
  5. kCFRunLoopBeforeWaiting:即将进入休眠
  6. CFRunLoopSetSleeping:睡眠
  7. CFRunLoopServiceMachPort:do...while循环处理msg,没有则退出
  8. kCFRunLoopAfterWaiting:休眠后即将唤醒, 唤醒后处理消息,处理完回到第一步。

1.2其他

  1. kCFRunLoopEntry:进入runloop循环开始、kCFRunLoopExit:退出runloop循环结束
  2. 在循环开始前,有一个GCD定时器,处理超时机制,默认间隔1.0e10
  3. 怎么通知观察者?
    循环取出CFRunLoopModeRef中的observers,再循环逐个回调CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
  4. source事件处理
  • 处理source0事件: __CFRunLoopDoBlocks-> CFRunLoopDoSources0()
  • CFRunLoopServiceMachPort(接受source1事件,goto handle_msg处理source1)
  1. kCFRunLoopBeforeWaiting
    即将进入休眠,后面有一个嵌套了do...while内循环用于接收等待端口消息,进入此循环后,线程进入休眠,直到收到新消息才跳出该循环
    CFRunLoopServiceMachPort:这个函数会睡眠线程
  2. kCFRunLoopAfterWaiting
    即将唤醒线程处理消息
  3. handle_msg
    根据livePort处理消息:包括是否继续运行、timer事件
  4. retVal 状态判断runloop后面的状态
    包括超时、完成、停止、处理source事件
    以上没有则继续运行

2.RunLoop的基本数据结构

2.1 CFRunLoop、CFRunLoopModel、source/Timer/Observer

  1. 从CFRunLoop结构得出结论
  • 也就是和线程是一一对应的;
    以及若干个Mode、若干个commonModeItem,还有一个当前运行的CurrentMode。
  • 如果在RunLoop中需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
  1. CFRunLoopMode
  • CFRunLoopMode对象有一个name,若干source0、source1、timer、observer和若干port,其中source,timer,observer 数据结构被统称为 mode item。
  • source、timer、observer可以在多个model中注册,但是只有runloop当前的currentMode下的source、timer、observer才可以运行。
  1. CFRunLoopSource
    __CFRunLoopSource是事件产生的地方。Source有两个版本:Source0 和 Source1。
    source0 只包含了一个回调(函数指针),source0是需要手动触发的Source,它并不能主动触发事件,必须要先把它标记为signal状态。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,也就是通过uint32_t _bits来实现的,只有_bits标记Signaled状态才会被处理。然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
    source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。简单来说就是更加偏向于底层。
  2. CFRunLoopTimer
    它和 NSTimer 是toll-free bridged 的(资料可以看这里),可以混用。其包含一个时间长度和一个回调(函数指针)。
  3. CFRunLoopObserver
    CFRunLoopObserver是观察者,可以观察RunLoop的各种状态,每个 Observer 都包含了一个回调(也就是上面的CFRunLoopObserverCallBack函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。
  4. 小结:
  • CFRunLoopTimer与NSTimer可以相互转换,是runloop的源泉。
  • 一个runloop包含多个model,但一次只会处于一种model。如果想同时使用多个,使用commomModes。

3.RunLoop面试题

3.1 什么是RunLoop?
RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象。

  • 没有消息需要处理时,休眠以避免资源占用(用户态->内核态)
  • 有消息需要处理时,立刻被唤醒(内核态->用户态)

3.2 main为什么不退出
程序启动时,主线程会默认开启RunLoop循环

3.3 使用NSTimer时,滑动会停止吗?保证NSTimer在滑动时还能正常运行

  • 解决会停止,因为滑动时RunLoop进行了从 defaultmodel 状态切换 trackingModel 状态。
  • 解决方案,可以timer添加到common model中。

3.4 利用RunLoop空闲时计算数据
performSelector,如加载图片时 tableView滑动时不加载网络图片
(切换到UITrackingRunLoopMode)
performSelector:withObject:afterDelay:inModes

4.RunLoop卡顿原理及卡顿源码阅读

4.1原理

  1. 注册主线程runloop的观察者,添加到kCFRunLoopCommonModes
  2. 开启一个常驻子线程监听信号量
  3. runLoopObserverCallBack状态变化即发送信号
  4. 如果dispatch_semaphore_wait没有超时,即正常。
    单位:SEC秒、PER 每、NSEC纳秒、MSEC毫秒、USEC微秒
    1s = 1000 ms = 10 e 3 ms
    = 1000,000 us = 10 e 6 us
    = 1000,000,000 ns = 10 e 9 ns
    runloop的时间是:1/60s = 1000/60ms=16.7ms

4.2解释

  1. 利用量信号量超时的特性,如果正常响应没有卡顿,否则响应超时。
  2. 如果 RunLoop 的线程,进入睡眠前方法(kCFRunLoopBeforeSources)的执行时间过长而导致无法进入睡眠(进入休眠前会处理timer、source 0、source 1事件),或者线程唤醒后接收消息时间过长(kCFRunLoopAfterWaiting)而无法进入下一步的话,就可以认为是线程受阻了。

5.常驻线程

  1. 启动一个NSThread,在run方法中用@autoreleasepool包裹
  2. 获取当前currentRunLoop
  3. 添加[NSPort port],添加NSTimer
  4. 调用currentRunLoop的run方法

相关文章

网友评论

      本文标题:RunLoop

      本文链接:https://www.haomeiwen.com/subject/ukokqhtx.html