二. runloop 的数据结构
参考文章:
深入理解RunLoop | Garan no dou
Run Loop 记录与源码注释我对 runloop 源码的解析:
数据结构源码解析
CFRunloopRun 源码解析
一些其他函数解析所使用的源码版本:
CF-1151.16.zip
结合前面提到一.runloop 是什么 Runloop 中所具有的几个元素, 这里来谈谈数据结构
对于具体代码的阅读见 数据结构源码解析
这篇文章聊聊从源码中得出的结论
我们前文中得出了几个元素:
ThreadRunloopModeItem(Source,Timer,Observer)
其中的 Thread 在 CF 源码中调用的是 pthread_t 不做过多深入
剩余的元素, 关系如图:(省略了部分字段的)

Thread 与 Runloop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
- 全局的 Runloop 被一个
CFMutableDictionaryRef类型的字典存放起来 - 该字典以
Thread的 id 作为 key, 以Runloop作为 value Runloop与Thread为一对一的关系- 对外的创建方式只有获取, 而不能手动创建
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)
_CFRunLoopGet0
Runloop 与 Mode
Runloop以CFMutableSetRef集合的方式, 存放了很多 modes- 包含三个字段
_modes,_currentMode,_commonModes commonMode在新增Item和Runloop执 行过程都会被多次访问. 对于在_commonModes中的modes, 会在合适的时机,将_commonModeItems的内容都同步进去
// CFRunLoopAddSource
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetAddValue(rl->_commonModeItems, rls);
}
// CFRunLoopAddTimer
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetAddValue(rl->_commonModeItems, rlt);
}
// CFRunLoopAddObserver
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetAddValue(rl->_commonModeItems, rlo);
}
Mode
- 以集合
CFMutableSetRef存放Source Source又被分为source0,source1Observes及Timer均以数组存放- ❓其构造函数调用的, 暂时还没找到外部手动创建对应的逻辑
(CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL); - runloop 执行各种 item (
CFRunLoopDo)的过程中, 都会先对当前 mode 及 item 所属 mode 进行判断, 只有相符的 item 才会执行
其他: 一些其他函数解析(CFRunLoopDoObservers、__CFRunLoopDoBlocks、__CFRunLoopDoSources0) - 如果要切换 mode, 得先退出 loop 再重新进入, 单纯的切换 mode 没有自动停止功能.
- 执行过程中, 如果 mode 中没有内容, 也会直接退出
// CFRunLoopRunSpecific if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { Boolean did = false; return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; }
- 执行过程中, 如果 mode 中没有内容, 也会直接退出
CFRunloop 对外提供 Mode 接口用于处理:(可以看到,没有删除):
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
Item
Item 包含三个东西Source,Timer,Observer, 都是在 runloop 循环过程中会触发的实体.
其函数名很统一前缀都是__CFRunLoopDo, 在 __CFRunLoopRun 运行过程中,分别会调用:
__CFRunLoopDoObservers__CFRunLoopDoSources0__CFRunLoopDoTimers
其他: 一些其他函数解析(CFRunLoopDoObservers、__CFRunLoopDoBlocks、__CFRunLoopDoSources0)
其内部实现也很类似:
- 先从当前 mode 取出需要的 item
- 核心都是调用
__CFRUNLOOP_IS_CALLING_OUT_TO的函数, 这些函数最终都是拿到入参的函数指针, 然后执行DoObservers:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__DoSources0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__DoTimers:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- 内部都会先判断是否是集合(因为其指针可能是单个元素),是的话执行遍历,不是的话,当做单个元素处理:
DoSources0:CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()
总结来说: item 是带着函数指针的集合, 这个集合的函数指针, runloop 执行过程中会从当前 mode 里拿出来, 然后执行对应函数
ps.其实还有个block, 不是这儿的重点, 略
对于这些 Item, 由 Mode 暴露接口提供添加方法:
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
Source
source0,source1两个Source集合, 对应前文的Custom Input Sources,Port-Based Sources- 内部是两个不同字段的联合体(union), 也就是 2 选 1,都是在同一块内存空间
- 其中 source0 中的内容不会自动唤起线程, 需要手动调用唤醒
performselector是 iOS 为用户实现的系列函数, 也是 source0,
- source 1 由系统内核触发, 会自动唤起线程
- 其中 source0 中的内容不会自动唤起线程, 需要手动调用唤醒
-
执行函数:
source0的具体过程在这里做分析__CFRunLoopDoSources0), 内部校验等结束后调用CFRUNLOOP_IS_CALLING_OUT前缀的函数
(__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__, 其内部最终执行一个函数) - 如何加入自定义 source 主要参考官方文档 Configuring Run Loop Sources
Timer
对应的上层就是 NSTimer, 这也是为什么 NSTimer 运行必须依赖 runloop 的原因
据一些文章介绍 NSTimer 与 CFRunLoopTimerRef 可以相互转换
执行函数: 在 __CFRunLoopDoTimer, 内部核心也是一个CFRUNLOOP_IS_CALLING_OUT
(__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__, 其内部最终执行一个函数)
Timer 依赖 Runloop 的特性导致其不准时, 其运行与否基于 runloop 的状态
ps.我们常见的 NSTimer 在滑动时失效问题, 是因为 NSTimer 和滑动的 Mode 不是同一个
Observer
我们看执行函数 其他: CFRunloopRun 源码解析
会发现 Observer 的触发非常简单,状态都是直接写在代码里的,在 run 函数中, 特定位置的某一行, 加入一个判断, 然后调用执行函数
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
Observer 可以监听的类型:
typedef unsigned long CFOptionFlags;
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
执行函数: CFRunLoopDoObservers, 内部校验等结束后调用 CFRUNLOOP_IS_CALLING_OUT 前缀的函数
(__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__, 其内部最终执行一个函数)