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则有更广泛的用处。