1.源码阅读
1.1事件循环:do...while循环部分

- kCFRunLoopBeforeTimers:即将处理timer
- kCFRunLoopBeforeSources:即将处理source
- CFRunLoopDoSources0:处理source0事件
- CFRunLoopServiceMachPort:处理source1事件:读取msg_buffer,系统事件,如果有消息goto handle_msg
- kCFRunLoopBeforeWaiting:即将进入休眠
- CFRunLoopSetSleeping:睡眠
- CFRunLoopServiceMachPort:do...while循环处理msg,没有则退出
- kCFRunLoopAfterWaiting:休眠后即将唤醒, 唤醒后处理消息,处理完回到第一步。
1.2其他
- kCFRunLoopEntry:进入runloop循环开始、kCFRunLoopExit:退出runloop循环结束
- 在循环开始前,有一个GCD定时器,处理超时机制,默认间隔1.0e10
- 怎么通知观察者?
循环取出CFRunLoopModeRef中的observers,再循环逐个回调CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION - source事件处理
- 处理source0事件: __CFRunLoopDoBlocks-> CFRunLoopDoSources0()
- CFRunLoopServiceMachPort(接受source1事件,goto handle_msg处理source1)
- kCFRunLoopBeforeWaiting
即将进入休眠,后面有一个嵌套了do...while内循环用于接收等待端口消息,进入此循环后,线程进入休眠,直到收到新消息才跳出该循环
CFRunLoopServiceMachPort:这个函数会睡眠线程 - kCFRunLoopAfterWaiting
即将唤醒线程处理消息 - handle_msg
根据livePort处理消息:包括是否继续运行、timer事件 - retVal 状态判断runloop后面的状态
包括超时、完成、停止、处理source事件
以上没有则继续运行
2.RunLoop的基本数据结构
2.1 CFRunLoop、CFRunLoopModel、source/Timer/Observer
- 从CFRunLoop结构得出结论
- 也就是和线程是一一对应的;
以及若干个Mode、若干个commonModeItem,还有一个当前运行的CurrentMode。 - 如果在RunLoop中需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
- CFRunLoopMode
- CFRunLoopMode对象有一个name,若干source0、source1、timer、observer和若干port,其中source,timer,observer 数据结构被统称为 mode item。
- source、timer、observer可以在多个model中注册,但是只有runloop当前的currentMode下的source、timer、observer才可以运行。
- 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 的线程。简单来说就是更加偏向于底层。 - CFRunLoopTimer
它和 NSTimer 是toll-free bridged 的(资料可以看这里),可以混用。其包含一个时间长度和一个回调(函数指针)。 - CFRunLoopObserver
CFRunLoopObserver是观察者,可以观察RunLoop的各种状态,每个 Observer 都包含了一个回调(也就是上面的CFRunLoopObserverCallBack函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。 - 小结:
- 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原理
- 注册主线程runloop的观察者,添加到kCFRunLoopCommonModes
- 开启一个常驻子线程监听信号量
- runLoopObserverCallBack状态变化即发送信号
- 如果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解释
- 利用量信号量超时的特性,如果正常响应没有卡顿,否则响应超时。
- 如果 RunLoop 的线程,进入睡眠前方法(kCFRunLoopBeforeSources)的执行时间过长而导致无法进入睡眠(进入休眠前会处理timer、source 0、source 1事件),或者线程唤醒后接收消息时间过长(kCFRunLoopAfterWaiting)而无法进入下一步的话,就可以认为是线程受阻了。
5.常驻线程
- 启动一个NSThread,在run方法中用@autoreleasepool包裹
- 获取当前currentRunLoop
- 添加[NSPort port],添加NSTimer
- 调用currentRunLoop的run方法
网友评论