借助AI辅助。
__CFRunLoopDoTimers 和 __CFRunLoopDoTimer 函数详解
__CFRunLoopDoTimers 函数概述
__CFRunLoopDoTimers 是 RunLoop 中负责批量处理 Timer 的函数。它遍历当前 Mode 中的所有 Timer,收集需要触发的 Timer(触发时间 <= 当前时间),然后依次调用 __CFRunLoopDoTimer 执行每个 Timer 的回调。
函数职责
- 收集待触发的 Timers: 遍历 Mode 中的所有 timers,筛选出到期的
- 批量执行: 依次调用
__CFRunLoopDoTimer处理每个 timer - 返回状态: 告知 RunLoop 是否处理了至少一个 timer
与上层的关系
__CFRunLoopRun (主循环)
↓
├─ 计算 limitTSR(最大允许延迟时间)
↓
__CFRunLoopDoTimers(rl, rlm, limitTSR) ⭐ 本函数
↓
├─ 收集所有 fireTSR <= limitTSR 的 timers
├─ 遍历调用 ──→ __CFRunLoopDoTimer(rl, rlm, rlt)
│ ↓
│ 执行 timer 回调
│ ↓
│ 计算下次触发时间
└─ 返回是否处理了 timer
【完整代码逐行注释】
// rl and rlm are locked on entry and exit
// 📝 锁状态约定:函数入口和出口时,rl 和 rlm 都必须处于加锁状态
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */
// ⚠️ 重要标注:此函数会执行外部回调(通过 __CFRunLoopDoTimer)
// 可能导致长时间阻塞、重入问题、死锁风险
// 📊 记录性能追踪点:开始处理 Timers
// 参数:事件类型、RunLoop、Mode、limitTSR(时间限制)、额外参数
// 可用 Instruments 的 System Trace 查看
cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_TIMERS | DBG_FUNC_START, rl, rlm, limitTSR, 0);
// 标志位:记录是否至少处理了一个 timer
Boolean timerHandled = false;
// 用于存储收集到的待触发 timers 的数组
// 初始为 NULL,只在有需要触发的 timer 时才创建
CFMutableArrayRef timers = NULL;
// ==================== 第一阶段:收集待触发的 Timers ====================
// 遍历当前 Mode 中的所有 timers
// rlm->_timers 是一个 CFArray,包含所有添加到此 mode 的 timers
// 获取数组长度,如果 _timers 为 NULL 则 cnt = 0
for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
// 获取第 idx 个 timer
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
// 🔍 三重过滤条件(必须全部满足):
if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
// 条件1: __CFIsValid(rlt)
// 检查 timer 是否有效(未被 invalidate)
// timer 可能在之前的回调中被标记为无效
// 条件2: !__CFRunLoopTimerIsFiring(rlt)
// 检查 timer 是否正在执行回调
// 防止重入:如果 timer 的回调函数中触发了 RunLoop 循环,
// 不会重复调用该 timer(避免无限递归)
// 🕐 检查 timer 是否到期
// rlt->_fireTSR: timer 下次触发的时间戳(TSR = TimeStamp Register)
// limitTSR: 本次处理的时间上限
// 如果 _fireTSR <= limitTSR,说明 timer 应该触发了
if (rlt->_fireTSR <= limitTSR) {
// Timer 需要触发
// 💡 延迟创建数组优化
// 只在第一次发现需要触发的 timer 时才创建数组
// 避免没有到期 timer 时的无谓内存分配
if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
// 📦 将 timer 添加到收集数组
// CFArrayAppendValue 会自动 CFRetain(rlt)
CFArrayAppendValue(timers, rlt);
}
}
}
// ==================== 第二阶段:执行收集到的 Timers ====================
// 遍历收集到的待触发 timers
// 如果 timers 为 NULL(没有到期的 timer),cnt = 0,不会进入循环
for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
// 获取第 idx 个待触发的 timer
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
// ⚡ 调用 __CFRunLoopDoTimer 处理单个 timer
// 这个函数会:
// 1. 再次检查 timer 的有效性和触发条件
// 2. 执行 timer 的回调函数
// 3. 计算下次触发时间
// 4. 重新调整 timer 在 mode 中的位置
// 返回:是否成功执行了该 timer
Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
// 逻辑或运算:只要有一个 timer 被处理,timerHandled 就为 true
// 等价于:if (did) timerHandled = true;
timerHandled = timerHandled || did;
}
// 📉 释放 timers 数组
// 如果创建了数组,需要释放(减少引用计数)
// CFArrayAppendValue 时对每个 timer 进行了 CFRetain,
// CFRelease 数组时会对每个元素调用 CFRelease
if (timers) CFRelease(timers);
// 📊 记录性能追踪点:完成 Timers 处理
cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_TIMERS | DBG_FUNC_END, rl, rlm, limitTSR, 0);
// 返回是否至少处理了一个 timer
// true: 执行了至少一个 timer
// false: 没有执行任何 timer(没有到期的 timers 或所有 timers 都无效)
return timerHandled;
}
【函数执行流程图】
__CFRunLoopDoTimers
↓
📊 开始性能追踪
↓
初始化 timers = NULL
↓
┌───────────────────────────────────────┐
│ 第一阶段:收集待触发的 Timers │
└───────────────────┬───────────────────┘
↓
遍历 rlm->_timers 中的每个 timer
↓
┌───────────────┴───────────────┐
│ Is Valid && !IsFiring? │
└───────────────┬───────────────┘
│
┌─────────────┴─────────────┐
NO YES
↓ ↓
跳过此 timer ┌────────┴────────┐
│ fireTSR <= limit? │
└────────┬────────┘
│
┌───────────────┴───────────────┐
NO YES
↓ ↓
跳过此 timer 添加到 timers 数组
(延迟创建数组)
↓
┌───────────────────────────────────────┐
│ 第二阶段:执行收集到的 Timers │
└───────────────────┬───────────────────┘
↓
遍历 timers 数组中的每个 timer
↓
调用 __CFRunLoopDoTimer(rl, rlm, rlt)
↓
timerHandled |= did
↓
释放 timers 数组
↓
📊 结束性能追踪
↓
return timerHandled
【关键设计要点】
1. 两阶段处理模式
阶段1: 收集(持有锁) 阶段2: 执行(会解锁)
↓ ↓
遍历 _timers 数组 遍历收集的 timers 数组
↓ ↓
检查条件并收集 调用 __CFRunLoopDoTimer
↓ ↓
快速,不执行回调 慢速,执行用户回调
为什么要分两阶段?
| 原因 | 说明 |
|---|---|
| 安全性 | 在执行回调期间可能修改 rlm->_timers,如果边遍历边执行会导致数组变化 |
| 一致性 | 确保所有判断基于同一时刻的状态 |
| 可预测性 | 避免回调中添加/删除 timer 影响当前循环 |
2. 延迟创建数组优化
if (!timers) timers = CFArrayCreateMutable(...); // 只在需要时创建
场景分析:
情况1: 没有到期的 timer
- timers 保持为 NULL
- 不分配内存
- 第二阶段循环不执行(cnt = 0)
情况2: 有到期的 timer
- 第一次遇到时创建数组
- 添加所有到期的 timers
- 第二阶段执行
3. limitTSR 的作用
if (rlt->_fireTSR <= limitTSR)
limitTSR 的计算(在 __CFRunLoopRun 中):
// 通常是当前时间加上一个容忍度
uint64_t currentTSR = mach_absolute_time();
uint64_t limitTSR = currentTSR + toleranceTSR;
// 作用:
// - 允许稍微延迟的 timer 也在本次循环中执行
// - 减少唤醒次数,提高电池效率
// - 实现 timer coalescing(合并触发)
示例:
当前时间: 1000
tolerance: 10
limitTSR: 1010
Timer A: _fireTSR = 995 ✅ 执行(已过期)
Timer B: _fireTSR = 1005 ✅ 执行(在容忍度内)
Timer C: _fireTSR = 1015 ❌ 跳过(超出容忍度)
4. 防止重入的机制
if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt))
重入场景:
// Timer 的回调函数
void timerCallback(CFRunLoopTimerRef timer, void *info) {
// 在回调中再次运行 RunLoop(重入)
CFRunLoopRun();
// 如果没有 IsFiring 检查,这个 timer 可能被再次触发
// 导致无限递归
}
// 防护机制:
// 1. __CFRunLoopDoTimer 在执行前设置 IsFiring = true
// 2. __CFRunLoopDoTimers 检查 !IsFiring,跳过正在执行的 timer
// 3. __CFRunLoopDoTimer 执行完后设置 IsFiring = false
__CFRunLoopDoTimer 函数概述
函数概述
__CFRunLoopDoTimer 是 RunLoop 中负责执行单个 Timer 的核心函数。它负责:
- 检查 timer 是否应该触发
- 执行 timer 的回调函数
- 计算下次触发时间
- 重新调整 timer 在各个 mode 中的位置
- 处理一次性 timer 的失效
这是一个复杂的函数,涉及精细的锁管理、时间计算、多 mode 协调等。
完整代码逐行注释
// mode and rl are locked on entry and exit
// 📝 锁状态约定:函数入口和出口时,rl 和 rlm 都必须处于加锁状态
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { /* DOES CALLOUT */
// ⚠️ 重要标注:此函数会执行外部回调,可能导致长时间阻塞、重入、死锁
// 📊 记录性能追踪点:开始触发 Timer
cf_trace(KDEBUG_EVENT_CFRL_TIMERS_FIRING | DBG_FUNC_START, rl, rlm, rlt, 0);
// 标志位:记录 timer 是否被成功处理
Boolean timerHandled = false;
// 保存 timer 本次触发的时间戳
// 用于后续计算下次触发时间
uint64_t oldFireTSR = 0;
/* Fire a timer */
// 🔥 触发一个 timer
// ==================== 准备阶段:增加引用计数并加锁 ====================
// 📌 增加 timer 的引用计数
// 防止在执行期间 timer 被其他线程释放
// 必须在最后配对 CFRelease(rlt)
CFRetain(rlt);
// 🔒 锁定 timer 对象(细粒度锁)
__CFRunLoopTimerLock(rlt);
// ==================== 条件检查:四重验证 ====================
// 🔍 四重条件检查(必须全部满足才执行回调)
if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {
// 条件1: __CFIsValid(rlt)
// 检查 timer 是否有效(未被 invalidate)
// 在收集和现在执行之间,timer 可能被其他线程 invalidate
// 条件2: rlt->_fireTSR <= mach_absolute_time()
// 再次检查 timer 是否确实到期
// 使用当前精确时间,而不是 __CFRunLoopDoTimers 中的 limitTSR
// 这是最后一道防线,确保不会过早触发
// 条件3: !__CFRunLoopTimerIsFiring(rlt)
// 检查 timer 是否正在执行回调
// 防止重入(回调中再次运行 RunLoop)
// 条件4: rlt->_runLoop == rl
// 检查 timer 是否仍然属于这个 RunLoop
// timer 可能在执行前被移动到其他 RunLoop
// ==================== 子阶段1:准备回调上下文 ====================
// 用于存储 timer 的上下文信息
void *context_info = NULL;
// 上下文信息的释放函数(可选)
void (*context_release)(const void *) = NULL;
// 如果 timer 的上下文有 retain 函数,需要持有上下文
if (rlt->_context.retain) {
// 调用用户提供的 retain 函数
// 增加上下文对象的引用计数
// 防止回调执行期间上下文被释放
context_info = (void *)rlt->_context.retain(rlt->_context.info);
// 保存 release 函数,稍后需要调用
context_release = rlt->_context.release;
} else {
// 没有 retain 函数,直接使用原始指针
// ⚠️ 用户需要保证 info 在回调期间有效
context_info = rlt->_context.info;
}
// 判断 timer 是否是一次性的(non-repeating)
// interval = 0.0 表示不重复
Boolean doInvalidate = (0.0 == rlt->_interval);
// 🚩 设置"正在执行"标志位
// 防止重入(与 __CFRunLoopDoTimers 中的检查配合)
__CFRunLoopTimerSetFiring(rlt);
// ==================== 子阶段2:重置 Mode 的 Timer 截止时间 ====================
// Just in case the next timer has exactly the same deadlines as this one,
// we reset these values so that the arm next timer code can correctly
// find the next timer in the list and arm the underlying timer.
// 🕐 重置 mode 的 timer 截止时间为最大值
// 原因:如果下一个 timer 与当前 timer 的截止时间完全相同,
// 重置这些值可以让 __CFArmNextTimerInMode 正确找到列表中的下一个 timer
// 并设置底层的系统 timer
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;
// 🔓 解锁 timer
// 准备执行回调,需要在回调前解锁(防止死锁)
__CFRunLoopTimerUnlock(rlt);
// ==================== 子阶段3:保存旧的触发时间 ====================
// 🔒 锁定 RunLoop 的 timer 时间戳锁(细粒度锁)
// 这是一个专门用于保护 timer 时间相关字段的锁
__CFLock(&rl->_timerTSRLock);
// 保存 timer 本次触发的时间戳
// 用于后续计算下次触发时间
oldFireTSR = rlt->_fireTSR;
// 🔓 解锁 timer 时间戳锁
__CFUnlock(&rl->_timerTSRLock);
// ==================== 子阶段4:准备下一个 Timer ====================
// ⏰ 设置 mode 中下一个要触发的 timer
// 这个函数会:
// 1. 遍历 mode 中的所有 timers
// 2. 找到最早需要触发的 timer
// 3. 设置系统的底层 timer(mk_timer 或 dispatch_source)
// 4. 更新 mode 的 _timerSoftDeadline 和 _timerHardDeadline
__CFArmNextTimerInMode(rlm, rl);
// ==================== 子阶段5:解锁准备执行回调 ====================
// 🔓 解锁 RunLoop Mode
__CFRunLoopModeUnlock(rlm);
// 🔓 解锁 RunLoop
__CFRunLoopUnlock(rl);
// ⚠️ 为什么要解锁?
// 1. 防止死锁:回调函数中可能调用 RunLoop API
// 2. 避免长时间持锁:timer 回调可能执行耗时操作
// 3. 提高并发性:允许其他线程在回调执行期间访问 RunLoop
// ==================== 子阶段6:执行回调函数 ====================
// 提取回调函数指针
// 类型:void (*)(CFRunLoopTimerRef timer, void *info)
CFRunLoopTimerCallBack callout = rlt->_callout;
// 🔄 开始自动释放池
// 管理回调中创建的临时对象
CFRUNLOOP_ARP_BEGIN(NULL)
// 📊 记录性能追踪点:开始调用 Timer 回调
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_TIMER | DBG_FUNC_START, callout, rlt, context_info, 0);
// ⚡ 执行 timer 的回调函数
// 这是整个函数的核心目的!
// 宏展开后通常是:
// if (callout) {
// callout(rlt, context_info);
// }
//
// ⚠️ 回调中可能发生的事情:
// - UI 更新
// - 网络请求
// - 数据处理
// - 调用 CFRunLoopTimerSetNextFireDate 修改下次触发时间
// - 添加/移除其他 timers
// - 重入 RunLoop(CFRunLoopRun)
// - 长时间阻塞(如果回调写得不好)
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(callout, rlt, context_info);
// 📊 记录性能追踪点:结束调用 Timer 回调
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_TIMER | DBG_FUNC_END, callout, rlt, context_info, 0);
// 🔄 结束自动释放池
// 释放回调中创建的所有 autorelease 对象
CFRUNLOOP_ARP_END()
// ==================== 子阶段7:回调后处理 ====================
// 🔒 检查进程是否被 fork
// 如果在回调执行期间进程被 fork,需要重新初始化
CHECK_FOR_FORK();
// 如果是一次性 timer,需要 invalidate
if (doInvalidate) {
// ❌ 使 timer 失效
// 会从 RunLoop 中移除,并标记为无效
// 注意:这个函数本身也会执行 callout(通知回调)
CFRunLoopTimerInvalidate(rlt); /* DOES CALLOUT */
}
// 如果有上下文释放函数,调用它
if (context_release) {
// 减少上下文对象的引用计数
// 对应前面的 context_info = retain(...)
context_release(context_info);
}
// ==================== 子阶段8:重新加锁 ====================
// 🔒 重新锁定 RunLoop
__CFRunLoopLock(rl);
// 🔒 重新锁定 RunLoop Mode
__CFRunLoopModeLock(rlm);
// 🔒 重新锁定 timer
__CFRunLoopTimerLock(rlt);
// ✅ 标记:成功处理了 timer
timerHandled = true;
// 🚩 清除"正在执行"标志位
// 允许此 timer 在下次到期时再次被触发
__CFRunLoopTimerUnsetFiring(rlt);
}
// ==================== 计算下次触发时间 ====================
// 如果 timer 有效且本次被处理了
if (__CFIsValid(rlt) && timerHandled) {
/* This is just a little bit tricky: we want to support calling
* CFRunLoopTimerSetNextFireDate() from within the callout and
* honor that new time here if it is a later date, otherwise
* it is completely ignored. */
// 💡 这里有个微妙的设计:
// 我们希望支持在回调中调用 CFRunLoopTimerSetNextFireDate()
// 如果在回调中设置了一个更晚的时间,我们会采用它
// 否则,按照 interval 计算的时间
// 比较旧的触发时间和当前的触发时间
if (oldFireTSR < rlt->_fireTSR) {
/* Next fire TSR was set, and set to a date after the previous
* fire date, so we honor it. */
// 下次触发时间已经被设置(在回调中调用了 SetNextFireDate)
// 且设置的时间比之前的触发时间晚,所以我们采用它
// 🔓 解锁 timer
__CFRunLoopTimerUnlock(rlt);
// The timer was adjusted and repositioned, during the
// callout, but if it was still the min timer, it was
// skipped because it was firing. Need to redo the
// min timer calculation in case rlt should now be that
// timer instead of whatever was chosen.
// timer 在回调期间被调整和重新定位了,
// 但如果它仍然是最早的 timer,之前因为正在触发而被跳过了。
// 需要重新计算最早的 timer,以防 rlt 现在应该是那个 timer。
__CFArmNextTimerInMode(rlm, rl);
} else {
// 回调中没有修改触发时间,或者修改的时间更早(被忽略)
// 按照 interval 计算下次触发时间
// 下次触发时间(TSR 格式)
uint64_t nextFireTSR = 0LL;
// interval 对应的 TSR 值
uint64_t intervalTSR = 0LL;
// ==================== 子步骤1:计算 interval 的 TSR 值 ====================
// 如果 interval <= 0,timer 不重复
if (rlt->_interval <= 0.0) {
// intervalTSR 保持为 0
} else if (TIMER_INTERVAL_LIMIT < rlt->_interval) {
// interval 太大(超过限制),使用最大允许值
// TIMER_INTERVAL_LIMIT 通常是几年(防止溢出)
intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
} else {
// 正常情况:将秒转换为 TSR
intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
}
// ==================== 子步骤2:计算下次触发时间 ====================
// 检查是否会溢出
if (LLONG_MAX - intervalTSR <= oldFireTSR) {
// 加上 interval 会导致溢出,使用最大值
nextFireTSR = LLONG_MAX;
} else {
// 正常情况
if (intervalTSR == 0) {
// 15304159: Make sure we don't accidentally loop forever here
// 🐛 防止无限循环
// 如果 interval 为 0 但 timer 是 repeating 的,会导致死循环
CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
HALT;
}
// 获取当前时间
uint64_t currentTSR = mach_absolute_time();
// 从旧的触发时间开始
nextFireTSR = oldFireTSR;
// ⏰ 循环跳过已经过去的触发点
// 如果系统繁忙或休眠,可能错过了多个触发点
// 需要找到第一个未来的触发时间
//
// 示例:
// oldFireTSR = 1000, interval = 10, currentTSR = 1035
// 迭代1: nextFireTSR = 1010 <= 1035, 继续
// 迭代2: nextFireTSR = 1020 <= 1035, 继续
// 迭代3: nextFireTSR = 1030 <= 1035, 继续
// 迭代4: nextFireTSR = 1040 > 1035, 退出
// 最终: nextFireTSR = 1040
while (nextFireTSR <= currentTSR) {
nextFireTSR += intervalTSR;
}
}
// ==================== 子步骤3:更新 timer 在所有 mode 中的位置 ====================
// 获取 timer 所属的 RunLoop
// 可能为 NULL(如果 timer 已经被从 RunLoop 移除)
CFRunLoopRef rlt_rl = rlt->_runLoop;
if (rlt_rl) {
// timer 仍然在 RunLoop 中
// 📌 增加 RunLoop 的引用计数
// 防止在后续操作期间 RunLoop 被释放
CFRetain(rlt_rl);
// 获取 timer 关联的所有 modes 的数量
CFIndex cnt = CFSetGetCount(rlt->_rlModes);
// 📦 在栈上分配数组(如果数量合理)
// 用于存储所有 mode 名称
STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
// 获取所有 mode 名称(CFStringRef)
CFSetGetValues(rlt->_rlModes, (const void **)modes);
// To avoid A->B, B->A lock ordering issues when coming up
// towards the run loop from a source, the timer has to be
// unlocked, which means we have to protect from object
// invalidation, although that's somewhat expensive.
// 💡 锁顺序问题:
// 为了避免 A->B, B->A 的死锁问题(当从 source 向上访问 run loop 时),
// timer 必须被解锁,这意味着我们必须防止对象失效,
// 尽管这有点昂贵。
// 📌 对所有 mode 名称增加引用计数
// 防止在解锁期间 mode 被释放
for (CFIndex idx = 0; idx < cnt; idx++) {
CFRetain(modes[idx]);
}
// 🔓 解锁 timer
// 接下来要锁定多个 modes,需要先解锁 timer 避免死锁
__CFRunLoopTimerUnlock(rlt);
// 🔍 获取所有 mode 对象并锁定
for (CFIndex idx = 0; idx < cnt; idx++) {
// mode 的名称(CFStringRef)
CFStringRef name = (CFStringRef)modes[idx];
// 根据名称获取 mode 对象
// false 表示如果 mode 不存在,不创建
modes[idx] = (CFTypeRef)__CFRunLoopCopyMode(rlt_rl, name, false);
// 如果 mode 存在,锁定它
if (modes[idx]) {
__CFRunLoopModeLock((CFRunLoopModeRef)modes[idx]);
}
// 释放 mode 名称
CFRelease(name);
}
// 🔒 锁定 RunLoop 的 timer 时间戳锁
__CFLock(&rl->_timerTSRLock);
// 🕐 更新 timer 的触发时间
rlt->_fireTSR = nextFireTSR;
// 📅 更新 timer 的下次触发日期(CFAbsoluteTime 格式)
// 用于外部查询(CFRunLoopTimerGetNextFireDate)
rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
// 🔄 在所有 mode 中重新定位 timer
// 因为触发时间改变了,timer 在排序数组中的位置可能改变
for (CFIndex idx = 0; idx < cnt; idx++) {
CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
if (rlm) {
// 重新定位 timer 在 mode 的 _timers 数组中的位置
// true 表示需要重新计算 mode 的下一个触发时间
__CFRepositionTimerInMode(rlm, rlt, true);
}
}
// 🔓 解锁 timer 时间戳锁
__CFUnlock(&rl->_timerTSRLock);
// 🔓 逆序解锁所有 modes(与加锁顺序相反)
// reverse index here so we unlock in the right order
for (CFIndex idx = cnt - 1; idx >= 0; idx--) {
if (modes[idx] != NULL) {
// 解锁 mode
__CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
// 释放 mode 对象
CFRelease((CFRunLoopModeRef)modes[idx]);
}
}
// 📉 释放 RunLoop
CFRelease(rlt_rl);
} else {
// timer 不在 RunLoop 中(已被移除)
// 只更新时间,不需要重新定位
// 🔓 解锁 timer
__CFRunLoopTimerUnlock(rlt);
// 🔒 锁定 timer 时间戳锁
__CFLock(&rl->_timerTSRLock);
// 🕐 更新触发时间
rlt->_fireTSR = nextFireTSR;
// 📅 更新下次触发日期
rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
// 🔓 解锁 timer 时间戳锁
__CFUnlock(&rl->_timerTSRLock);
}
}
} else {
// timer 无效或未被处理
// 🔓 解锁 timer
__CFRunLoopTimerUnlock(rlt);
}
// 📉 释放 timer 的引用计数
// 对应开头的 CFRetain(rlt)
CFRelease(rlt);
// 📊 记录性能追踪点:完成 Timer 触发
cf_trace(KDEBUG_EVENT_CFRL_TIMERS_FIRING | DBG_FUNC_END, rl, rlm, rlt, 0);
// 返回是否成功处理了 timer
return timerHandled;
}
【函数执行流程图】
__CFRunLoopDoTimer
↓
📊 开始性能追踪
↓
📌 CFRetain(rlt)
↓
🔒 Lock timer
↓
┌───────────────────────────────────────────┐
│ 四重条件检查 │
│ 1. IsValid? │
│ 2. _fireTSR <= now? │
│ 3. !IsFiring? │
│ 4. _runLoop == rl? │
└───────────────────┬───────────────────────┘
│
┌─────────────┴─────────────┐
NO YES
↓ ↓
🔓 Unlock timer 准备回调上下文
↓ (retain context_info)
📉 CFRelease(rlt) ↓
↓ doInvalidate = (interval == 0)
📊 结束追踪 ↓
↓ 🚩 SetFiring = true
return false ↓
重置 mode 的 deadline
↓
🔓 Unlock timer
↓
保存 oldFireTSR
↓
⏰ ArmNextTimerInMode
↓
🔓 Unlock mode & RunLoop
↓
┌───────────────────────────────────┐
│ 执行回调 │
│ 🔄 AutoRelease Pool Begin │
│ 📊 开始追踪 │
│ ⚡ callout(rlt, context_info) │
│ 📊 结束追踪 │
│ 🔄 AutoRelease Pool End │
└───────────────┬───────────────────┘
↓
🔒 CHECK_FOR_FORK
↓
if (doInvalidate)
↓
CFRunLoopTimerInvalidate
↓
if (context_release)
↓
context_release(context_info)
↓
🔒 Lock RunLoop & mode & timer
↓
timerHandled = true
↓
🚩 SetFiring = false
↓
┌───────────────────────────────────────────┐
│ 计算下次触发时间 │
└───────────────────┬───────────────────────┘
│
┌─────────────┴─────────────┐
│ oldFireTSR < _fireTSR? │
│ (回调中修改了时间?) │
└─────────────┬─────────────┘
│
┌─────────────┴─────────────┐
YES NO
↓ ↓
采用新时间 按 interval 计算
↓ ↓
__CFArmNextTimerInMode 计算 nextFireTSR
↓
while (nextFireTSR <= now)
nextFireTSR += interval
↓
更新所有 modes 中的位置
↓
🔒 锁定所有 modes
↓
__CFRepositionTimerInMode
↓
🔓 逆序解锁 modes
↓
📉 CFRelease(rlt)
↓
📊 结束追踪
↓
return timerHandled
【关键设计要点】
1. 四重条件检查
if (__CFIsValid(rlt) &&
rlt->_fireTSR <= mach_absolute_time() &&
!__CFRunLoopTimerIsFiring(rlt) &&
rlt->_runLoop == rl)
| 条件 | 目的 | 防止的问题 |
|---|---|---|
IsValid | 确认未 invalidate | 访问已释放的对象 |
_fireTSR <= now | 确认确实到期 | 过早触发 |
!IsFiring | 确认未在执行 | 重入、无限递归 |
_runLoop == rl | 确认属于此 RunLoop | 在错误的 RunLoop 中触发 |
2. 支持回调中修改触发时间
if (oldFireTSR < rlt->_fireTSR) {
// 回调中调用了 CFRunLoopTimerSetNextFireDate,设置了更晚的时间
// 采用新时间
} else {
// 按 interval 计算
}
使用场景:
void timerCallback(CFRunLoopTimerRef timer, void *info) {
// 动态调整下次触发时间
if ([self shouldDelayNextFire]) {
CFRunLoopTimerSetNextFireDate(timer, CFAbsoluteTimeGetCurrent() + 10.0);
}
}
3. 跳过错过的触发点
while (nextFireTSR <= currentTSR) {
nextFireTSR += intervalTSR;
}
示例:
Timer 每 1 秒触发一次
上次触发: 10:00:00
系统休眠 5 秒
当前时间: 10:00:05
计算过程:
nextFireTSR = 10:00:01 (错过了)
nextFireTSR = 10:00:02 (错过了)
nextFireTSR = 10:00:03 (错过了)
nextFireTSR = 10:00:04 (错过了)
nextFireTSR = 10:00:05 (错过了)
nextFireTSR = 10:00:06 ✅ 下次触发
结果:不会补发 5 次,直接跳到下一次未来的触发点
4. 多 Mode 协调的锁管理
// 避免死锁的锁顺序:
1. 解锁 timer
2. 锁定所有 modes(按索引顺序)
3. 锁定 _timerTSRLock
4. 更新时间
5. 在所有 modes 中重新定位
6. 解锁 _timerTSRLock
7. 逆序解锁所有 modes(与加锁顺序相反)
为什么要逆序解锁?
- 遵循 LIFO(后进先出)原则
- 与加锁顺序相反,减少死锁风险
- 最后锁定的资源最先释放
5. 一次性 Timer 的处理
Boolean doInvalidate = (0.0 == rlt->_interval);
if (doInvalidate) {
CFRunLoopTimerInvalidate(rlt);
}
生命周期:
创建: interval = 0
↓
添加到 RunLoop
↓
触发: __CFRunLoopDoTimer
↓
执行回调
↓
CFRunLoopTimerInvalidate <- 自动失效
↓
从 RunLoop 移除
↓
销毁
【性能优化技巧】
1. 使用合理的 tolerance
// ❌ 不好:tolerance = 0(严格触发)
CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(NULL, fireDate, 1.0, 0, 0, ^{
// 每次都要精确唤醒,耗电
});
// ✅ 好:设置合理的 tolerance
CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(NULL, fireDate, 1.0, 0, 0, ^{
// 处理
});
CFRunLoopTimerSetTolerance(timer, 0.1); // 100ms 容忍度
// 允许系统合并唤醒,省电
2. 避免在回调中做耗时操作
// ❌ 阻塞主线程
void timerCallback(CFRunLoopTimerRef timer, void *info) {
sleep(1); // 阻塞 1 秒
// 影响其他 timers 和事件处理
}
// ✅ 异步处理
void timerCallback(CFRunLoopTimerRef timer, void *info) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时操作
});
}
3. 动态调整触发频率
void timerCallback(CFRunLoopTimerRef timer, void *info) {
if ([self isUserActive]) {
// 用户活跃:频繁更新
CFRunLoopTimerSetNextFireDate(timer, CFAbsoluteTimeGetCurrent() + 0.1);
} else {
// 用户不活跃:降低频率
CFRunLoopTimerSetNextFireDate(timer, CFAbsoluteTimeGetCurrent() + 1.0);
}
}
【总结】
这两个函数体现了 RunLoop Timer 机制的精妙设计:
__CFRunLoopDoTimers(批量处理)
- 两阶段处理: 收集 + 执行,保证安全性和一致性
- 延迟创建优化: 没有到期 timer 时不分配内存
- 防重入机制: IsFiring 标志位防止无限递归
- 时间容忍度: limitTSR 实现 timer coalescing
__CFRunLoopDoTimer(单个处理)
- 四重条件检查: 确保安全执行
- 精细的锁管理: 回调前解锁,多 mode 协调
- 灵活的时间计算: 支持回调中修改触发时间
- 智能跳过: 不补发错过的触发点
- 多 mode 同步: 在所有关联的 modes 中更新 timer 位置
这些设计使得 RunLoop Timer 既高效又安全,是 iOS/macOS 定时任务的基础设施。