引子

NSTimer 是 iOS Foundation 框架中一种计时器,在经过必定的时间距离后触发,向方针目标发送指定的消息。

本文以标题为主线,探究 NSTimer 与 Runloop 之间的关系。

咱们先看下面这段代码的运行:

【iOS】NSTimer Block 为什么不会触发循环引用?!
场景: ViewController –Present-> SecondViewController。其间 Manager 实例会持有 Block。 SecondViewController 中有两个点击按钮,test1 按钮和 test2 按钮分别调度办法 -didTapTest1:-didTapTest2:

流程:

  1. 点击 xx 按钮;
  2. 等候数秒,打印 block 中的内容;
  3. 关闭 SecondViewController 控制器;

点击 test1 按钮履行流程,打印:

2023-01-25 16:59:08.712191+0800 BlockMemoryLeaks[27573:1465591] Manager: <Manager: 0x6000002e40c0>

点击 test2 按钮履行流程,打印:

2023-01-25 17:01:47.305332+0800 BlockMemoryLeaks[27622:1467958] Timer: <__NSCFTimer: 0x6000000400c0> 2023-01-25 17:01:48.305246+0800 BlockMemoryLeaks[27622:1467958] Timer: <__NSCFTimer: 0x6000000400c0> 2023-01-25 17:01:49.141697+0800 BlockMemoryLeaks[27622:1467958] SecondViewController dealloc

test1 Manager 和预期相同,Manager -> Block,Block -> self,self -> Manager,造成了循环引证。

而 test2 NSTimer 尽管被 self 持有,这个 Block 也捕获了 self,但这并没有触发循环引证。

NSTimer 与 Runloop

苹果关于 NSTimer 文档中有描述,NSTimer 和 CFRunLoopTimerRef 是 toll-free bridged 的:

NSTimer is toll-free bridged with its Core Foundation counterpart, CFRunLoopTimerRef.

NSTimer 是中间桥接层,意味着其实定时器运作是交给 Runloop 处理的。

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

即便是 NSTimer 是中间层,假如底层 Timer 持有了 Block,仍是存在循环引证。接着阅览 Runloop 代码来找到标题问题的答案。

RunLoop 的 Mode

CFRunloop 与 CFRunloop Mode 的大致结构如下:

struct __CFRunLoopMode {
    CFStringRef _name;            
    CFMutableSetRef _sources0;    
    CFMutableSetRef _sources1;    
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;    
    // ...
};
struct __CFRunLoop {
    CFRuntimeBase _base;
    CFMutableSetRef _commonModes;   
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;  
    CFMutableSetRef _modes;         
    // ...
};

一个 CFRunLoop 中包括若干个 CFRunLoopMode,CFRunLoopTimer 则被注册在 mode 下。

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;		/* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};

CFRunLoopTimer 包括一个时间长度和一个回调,标记了它地点的 runloop mode。

从增加 Timer 开始

先看 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 是 CommonModes */
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* 将 Timer 加入到一切 Common Mode 中 */
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
        /* Mode 是指定 Mode */
        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) {
                // 标记 Timer 对应的 Runloop
                rlt->_runLoop = rl;
            } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                return;
            }
            // 标记 Timer 对应的 Runloop Mode
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            /* 从头排序指定 Mode 中的各个 Timer */
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    __CFRunLoopUnlock(rl);
}

CFRunLoopAddTimer(_:_:_:) 将 Timer 增加到 Runloop 的指定 Mode 下。假如被增加的是 commonModes 则遍历一切 commonMode 调用 CFRunLoopAddTimer(_:_:_:) 办法。然后调用 __CFRepositionTimerInMode 函数排序:

static void __CFRepositionTimerInMode(CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt, Boolean isInArray) {
    if (!rlt) return;
    // 拿到 Mode 下一切 timer
    CFMutableArrayRef timerArray = rlm->_timers;
    if (!timerArray) return;
    Boolean found = false;
    if (isInArray) {
        CFIndex idx = CFArrayGetFirstIndexOfValue(timerArray, CFRangeMake(0, CFArrayGetCount(timerArray)), rlt);
        if (kCFNotFound != idx) {
            CFRetain(rlt);
            CFArrayRemoveValueAtIndex(timerArray, idx);
            found = true;
        }
    }
    if (!found && isInArray) return;
    // 二分法确定方位,插入有序数组
    CFIndex newIdx = __CFRunLoopInsertionIndexInTimerArray(timerArray, rlt);
    CFArrayInsertValueAtIndex(timerArray, newIdx, rlt);
    // 
    __CFArmNextTimerInMode(rlm, rlt->_runLoop);
    if (isInArray) CFRelease(rlt);
}

__CFRepositionTimerInMode 办法以触发时间 _fireTSR 从小到大排序 rlm->timers Mode 的 timers 数组。 排序完成后调用 __CFArmNextTimerInMode 从头注册最早应该被触发的 timer

static void __CFArmNextTimerInMode(CFRunLoopModeRef rlm, CFRunLoopRef rl) {
    uint64_t nextHardDeadline = UINT64_MAX;
    uint64_t nextSoftDeadline = UINT64_MAX;
    if (rlm->_timers) {
        // 修正 tolerance 值,保证时间最近的 timer + tolerance 大于其他 Timer 时,不影响其他 Timer 触发
        for (CFIndex idx = 0, cnt = CFArrayGetCount(rlm->_timers); idx < cnt; idx++) {
            CFRunLoopTimerRef t = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers , idx);
            if (__CFRunLoopTimerIsFiring(t)) continue;
            int32_t err = CHECKINT_NO_ERROR;
            uint64_t oneTimerSoftDeadline = t->_fireTSR;
            uint64_t oneTimerHardDeadline = check_uint64_add(t->_fireTSR, __CFTimeIntervalToTSR(t->_tolerance), &err);
            if (err != CHECKINT_NO_ERROR) oneTimerHardDeadline = UINT64_MAX;
            if (oneTimerSoftDeadline > nextHardDeadline) {
                break;
            }
            if (oneTimerSoftDeadline < nextSoftDeadline) {
                nextSoftDeadline = oneTimerSoftDeadline;
            }
            if (oneTimerHardDeadline < nextHardDeadline) {
                nextHardDeadline = oneTimerHardDeadline;
            }
        }
        if (nextSoftDeadline < UINT64_MAX && (nextHardDeadline != rlm->_timerHardDeadline || nextSoftDeadline != rlm->_timerSoftDeadline)) {
            if (CFRUNLOOP_NEXT_TIMER_ARMED_ENABLED()) {
                CFRUNLOOP_NEXT_TIMER_ARMED((unsigned long)(nextSoftDeadline - mach_absolute_time()));
            }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
            uint64_t leeway = __CFTSRToNanoseconds(nextHardDeadline - nextSoftDeadline);
            dispatch_time_t deadline = __CFTSRToDispatchTime(nextSoftDeadline);
#if USE_MK_TIMER_TOO
            if (leeway > 0) {
                // 有 tolerance 选用 _dispatch_source_set_runloop_timer_4CF 办法注册定时器
                if (rlm->_mkTimerArmed && rlm->_timerPort) {
                    AbsoluteTime dummy;
                    mk_timer_cancel(rlm->_timerPort, &dummy);
                    rlm->_mkTimerArmed = false;
                }
                _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, deadline, DISPATCH_TIME_FOREVER, leeway);
                rlm->_dispatchTimerArmed = true;
            } else {
                // 没有 tolerance 选用 RunloopMode 的 mk_timer 办法注册 mach-port 事情
                if (rlm->_dispatchTimerArmed) {
                    _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 888);
                    rlm->_dispatchTimerArmed = false;
                }
                if (rlm->_timerPort) {
                    mk_timer_arm(rlm->_timerPort, __CFUInt64ToAbsoluteTime(nextSoftDeadline));
                    rlm->_mkTimerArmed = true;
                }
            }
#else
            _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, deadline, DISPATCH_TIME_FOREVER, leeway);
#endif
#else
            if (rlm->_timerPort) {
                mk_timer_arm(rlm->_timerPort, __CFUInt64ToAbsoluteTime(nextSoftDeadline));
            }
#endif
        } else if (nextSoftDeadline == UINT64_MAX) {
            // 假如没有定时器安排,则免除定时器,将 _mkTimerArmed 值置为 false
            if (rlm->_mkTimerArmed && rlm->_timerPort) {
                AbsoluteTime dummy;
                mk_timer_cancel(rlm->_timerPort, &dummy);
                rlm->_mkTimerArmed = false;
            }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
            if (rlm->_dispatchTimerArmed) {
                _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 333);
                rlm->_dispatchTimerArmed = false;
            }
#endif
        }
    }
    // 设置 Runloop Mode 的两个截止时间字段 Deadline
    rlm->_timerHardDeadline = nextHardDeadline;
    rlm->_timerSoftDeadline = nextSoftDeadline;
}

这个办法简略来说便是注册下一个需求触发的 Timer 事情到 Runloop 中。其间有配置 tolerance 的 Timer 会被注册为一个 GCD Timer,未配置 tolerance 的 Timer 截止时间会被注册一个 mach-port 事情,设置到 Runloop Mode 中。

触发 TimerCallBack

等到 Runloop 被 timer mach-port 唤醒时,调用 __CFRunLoopDoTimers 函数,挑选 _fireTSR 早于当时时间的 timers:

static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {	/* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                // 挑选 _fireTSR 早于当时时间的 timers
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        // 挑选后的 timer 顺次调用 __CFRunLoopDoTimer
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

接下来 timers 顺次调用 __CFRunLoopDoTimer,这个办法中会调用 timer 的任务 rlt->_callout,从头排序 timer,注册下一个 timerPort。

定论

从增加 Timer 到触发 Timer,剖析了 Runloop Mode 的数据结构,自始至终 CFRunLoopTimer 都在被 Runloop 办理。NSTimer 目标仅是 Foundation 到 CoreFoundation 联接的目标,经过 NSTimer 能够匹配操作到 CFRunLoopTimerRef 目标,而 CoreFoundation 的目标已经由底层 CFRetainCFRelease 办法正确 Retain 和 Release。不存在循环引证。