Runloop 学习之 CFRunloopTimer

本文详细探讨了CFRunloopTimer的定义、回调机制及其实现,包括如何添加和移除timer,以及与NSTimer和CADisplayLink的区别。CFRunloopTimer在内部转化为GCD timer并注册到runloop,确保在指定模式下触发回调。尽管有时间误差容忍度,但错过的时间点不会补回。CADisplayLink提供与屏幕刷新率同步的定时器,适合帧动画,而NSTimer适用于更广泛场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CFRunloopTimer

CFRunloopTimer 定义
struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;  //标记fire状态
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;        //添加该timer的runloop
    CFMutableSetRef _rlModes;     //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;     //理想时间间隔  /* immutable */
    CFTimeInterval _tolerance;    //时间偏差      /* mutable */
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

结论:

  • 一个 timer 只能放在一个 runloop 里
  • 一个 timer 可以放在不同的 mode 里

CFRunloopTimer 和 NSTimer 是 toll-free bridged 的,可以相互转换

timer 回调
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__()
CFRunloopTimer 实现
CFRunloopAddTimer
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
        // 若 mode 为 common mode
	CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	if (NULL == rl->_commonModeItems) {
	    rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	}
        // commonModelItems 增加该 timer
	CFSetAddValue(rl->_commonModeItems, rlt);
	if (NULL != set) {
	    CFTypeRef context[2] = {rl, rlt};
	    /* add new item to all common-modes */
        // 遍历每一个 commonMode,添加该 timer.
	    CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	    CFRelease(set);
	}
    } else {
	CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
	}
	if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
		rlt->_runLoop = rl;
  	    } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
	        __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
		return;
	    }
  	    CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
        // 删除 timers 中的该 timer,并新增该 timer
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
	}
        if (NULL != rlm) {
	    __CFRunLoopModeUnlock(rlm);
	}
    }
    __CFRunLoopUnlock(rl);
}
  • 若为 common mode, 则往 commonMode 和 commonModeItems 添加该 timer.
  • 替换 mode->timers 的 timer.
CFRunloopRemoveTimer
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
        // 若 mode 为 common
	if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rlt)) {
        // 若 runloop 的 commonModeItems 不为空;且,runloop commonModeItems 已经含有 runloop timer 了.
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        // commonModeItems 删除 runloop timer.
	    CFSetRemoveValue(rl->_commonModeItems, rlt);
	    if (NULL != set) {
		CFTypeRef context[2] = {rl, rlt};
		/* remove new item from all common-modes */
        // commonModes 删除 runloop timer.
		CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
		CFRelease(set);
	    }
	} else {
	}
    } else {
        // 根据 mode name 找到 mode
	CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
        CFIndex idx = kCFNotFound;
        CFMutableArrayRef timerList = NULL;
        if (NULL != rlm) {
            // 查找 timer 在 mode 的 timers 的位置
            timerList = rlm->_timers;
            if (NULL != timerList) {
                idx = CFArrayGetFirstIndexOfValue(timerList, CFRangeMake(0, CFArrayGetCount(timerList)), rlt);
            }
        }
        if (kCFNotFound != idx) {
            // 如果 mode 的 timers 里有 timer,则删除该 timer 的 mode.
            __CFRunLoopTimerLock(rlt);
            CFSetRemoveValue(rlt->_rlModes, rlm->_name);
            if (0 == CFSetGetCount(rlt->_rlModes)) {
                rlt->_runLoop = NULL;
            }
            __CFRunLoopTimerUnlock(rlt);
            // 删除 mode timers 中的 timer.
	    CFArrayRemoveValueAtIndex(timerList, idx);
            __CFArmNextTimerInMode(rlm, rl);
        }
        if (NULL != rlm) {
	    __CFRunLoopModeUnlock(rlm);
	}
    }
    __CFRunLoopUnlock(rl);
}
  • 若 mode 为 common,则从 commonMode, commonModeItems 删除 timer.
  • 删除 mode->timers 里的该 timer.
__CFArmNextTimerInMode

CFRunloopTimer 会在底层转为 GCD timer,并注册一个 port,在重复的时间点唤醒 runloop。
虽然 CFRunloopTimer 在底层转为 GCD timer,但仍然是通过 runloop 触发回调的。

NSTimer
  • NSTimer 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。
  • RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。
  • NSTimer 的执行必须依赖于 RunLoop,如果没有 RunLoop,NSTimer 是不会执行的。
  • 如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。
CADisplayLink
  • CADisplayLink是一个执行频率(fps)和屏幕刷新相同(可以修改preferredFramesPerSecond改变刷新频率)的定时器,它也需要加入到RunLoop才能执行。
    • 与NSTimer类似,CADisplayLink同样是基于CFRunloopTimerRef实现,底层使用mk_timer(可以比较加入到RunLoop前后RunLoop中timer的变化)。和NSTimer相比它精度更高(尽管NSTimer也可以修改精度),不过和NStimer类似的是如果遇到大任务它仍然存在丢帧现象。
  • 通常情况下CADisaplayLink用于构建帧动画,看起来相对更加流畅,而NSTimer则有更广泛的用处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值