[iOS] NSTimer ブロックが循環参照をトリガーしないのはなぜですか? !

プライマー

NSTimer はiOS Foundationフレームワークのタイマー、一定の時間間隔の後にトリガーされ、指定されたメッセージをターゲット オブジェクトに送信します。

この記事では、NSTimer と Runloop の関係を調べるために、タイトルを主な行として取り上げます。

最初に次のコードの操作を見てみましょう: メモリ.pngScene: ViewController --Present-> SecondViewController. その中で、Manager インスタンスが Block を保持します。SecondViewController には 2 つのクリック ボタンがあります。test1ボタンとtest2ボタンはそれぞれディスパッチ メソッド-didTapTest1:とボタンです。-didTapTest2:

プロセス:

  1. xx ボタンをクリックします。
  2. 数秒待ってから、ブロック内のコンテンツを出力します。
  3. SecondViewController コントローラーを閉じます。

test1 ボタンをクリックしてプロセスを実行し、印刷します。

2023-01-25 16:59:08.712191+0800 BlockMemoryLeaks[27573:1465591] マネージャー: <マネージャー: 0x6000002e40c0>

test2 ボタンをクリックしてプロセスを実行し、印刷します。

2023-01-25 17:01:47.305332+0800 BlockMemoryLeaks[27622:1467958] タイマー: <__NSCFTimer: 0x6000000400c0> 2023-01-25 17:01:48.305246+0800 BlockMemoryLeaks[27622:1 467958] タイマー: <__NSCFTimer: 0x6000000400c0> 2023-01-25 17:01:49.141697+0800 BlockMemoryLeaks[27622:1467958] SecondViewController の解放

test1 Manager は、予想どおり、Manager -> Block,Block -> self,self -> Manager循環参照を引き起こしました。

test2 NSTimerselfhold されていますが、この Block も自己をキャプチャしますが、これは循環参照をトリガーしません。

NSTimer とランループ

Apple の NSTimerドキュメントで説明されているように、NSTimer と CFRunLoopTimerRef は無料でブリッジされています。

NSTimer は、Core Foundation の対応する CFRunLoopTimerRef と無料でブリッジされています。

NSTimer は中間ブリッジ レイヤーです。これは、タイマー操作が実際に Runloop に渡されることを意味します。

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
复制代码

NSTimer が中間層であっても、基になる Timer がブロックを保持している場合でも、循環参照が存在します。次に、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。

结论

タイマーの追加からタイマーのトリガーまで、Runloop Mode のデータ構造が分析され、CFRunLoopTimer は最初から最後まで Runloop によって管理されています。NSTimer オブジェクトは、Foundation から CoreFoundation への接続オブジェクトのみです.NSTimer は、CFRunLoopTimerRef オブジェクトに一致して動作することができ、CoreFoundation オブジェクトはCFRetainCFRelease基になるおよびメソッドによって適切に保持および解放されています。循環参照はありません。

おすすめ

転載: juejin.im/post/7194064273960599611