runloop 原理

214 阅读8分钟

外围函数

__CFRunLoopRun

runloop 的开启最终都是调用 __CFRunLoopRun 函数。它的函数定义是:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)

它本身是个循环,但只是在一个指定的 mode 中循环,它依据参数决定什么时候退出循环:

  • seconds:超时退出循环
  • stopAfterHandle:处理任意一个 source1/source0/GCD block 就退出循环

对外方法

runloop 对外的方法有:

  • CFRunLoopRunInMode:调用 CFRunLoopRunSpecific,没什么特别的
  • CFRunLoopRunSpecific:会先保存现在loop的_per_run_data 和 mode,再执行__CFRunLoopRun,结束后再恢复原值,我猜测每次恢复的值都是开启runloop时的初始值
  • CFRunLoopRun:上面两个方法都只是包装了一层__CFRunLoopRun,调用的结果还是一层循环。而CFRunLoopRun本身是个循环,除非runloop执行结果是stop或者finished,否则将会一遍遍地执行 __CFRunLoopRun

系统使用 CFRunLoopRunSpecific 应该也是会像 CFRunLoopRun 这样包一层循环,但是判断条件不会这么简单,而且也不会只是在 default mode 下执行。后面我们会按照 CFRunLoopRun的做法模拟下tableview的 tracking mode 的切换。

void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
	Boolean did = false;
	if (currentMode) __CFRunLoopModeUnlock(currentMode);
	__CFRunLoopUnlock(rl);
	return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
	rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

__CFRunLoopRun 的实现

看了源码之后,我有非常多的疑问,基本上是,每个字我都认识,就是凑一起就不明白了:

  1. 为什么只有主线程&主队列&common mode时,dispatchPort 有值?
  2. 为什么一次循环中调用那么多次__CFRunLoopDoBlocks,它怎么那么牛逼?是GCD给的block吗?是performSelector给的block吗?
  3. poll 是要退出循环的意思吗?为什么 poll == true 时,不通知观察者即将进入/退出休眠
  4. 一次循环只处理一次 livePort,万一有多个事件到来怎么办?

这是源码,把 windows 的条件编译去掉了之后的(可以不看,后面还有简化版本):

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl); 
	return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	rlm->_stopped = false;
	return kCFRunLoopRunStopped;
    }
    
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) {
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
	dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
	timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
	timeout_context->ds = timeout_timer;
	timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
	timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
    dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
	dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
    } else { // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }

    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        uint8_t msg_buffer[3 * 1024];
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
    	__CFPortSet waitSet = rlm->_portSet;

        __CFRunLoopUnsetIgnoreWakeUps(rl);
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

    	__CFRunLoopDoBlocks(rl, rlm);

        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
	    }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                goto handle_msg;
            }
        }

        didDispatchPortLastTime = false;
	if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	__CFRunLoopSetSleeping(rl);

        __CFPortSetInsert(dispatchPort, waitSet);
        
	__CFRunLoopModeUnlock(rlm);
	__CFRunLoopUnlock(rl);

        if (kCFUseCollectableAllocator) {
            objc_clear_stack(0);
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);        
        
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);

        // Must remove the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced. Also, we don't want them left
        // in there if this function returns.

        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);

        // user callouts now OK again
	__CFRunLoopUnsetSleeping(rl);
	if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
        }
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
            // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
	        __CFRunLoopLock(rl);
	        __CFRunLoopModeLock(rlm);
 	        sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
		mach_msg_header_t *reply = NULL;
		sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		if (NULL != reply) {
		    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
		}
	    }
        } 
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        
	__CFRunLoopDoBlocks(rl, rlm);
        

	if (sourceHandledThisLoop && stopAfterHandle) {
	    retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
	} else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
	    retVal = kCFRunLoopRunStopped;
	} else if (rlm->_stopped) {
	    rlm->_stopped = false;
	    retVal = kCFRunLoopRunStopped;
	} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
	    retVal = kCFRunLoopRunFinished;
	}
    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

简化版本:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    // 1. 判断runloop/mode的状态,不符合运行条件的,就返回
    
    // 2. 如果是在主线程&主队列&common mode,dispatchPort = _dispatch_get_main_queue_port_4CF(),否则就是空值
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
    // 3. 处理 runloop 的超时管理
    
    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        // 4. 为一会儿处理source1创建对象
        
        // 5. runloop 开始接受唤醒
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        // 6. 通知开始处理 timer/source0
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // 7. 处理 block
	    __CFRunLoopDoBlocks(rl, rlm);

        // 8. sourceHandledThisLoop:刚才是否处理了任意一个source0,下面处理过source1/GCD block后,也会改为true
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 9. 如果处理了任意一个source0,再一次处理 block
            __CFRunLoopDoBlocks(rl, rlm);
	    }

        // 是否要退出循环了
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            // 10. 在主线程,并且上一次循环中没有 source1 消息(第一次循环时didDispatchPortLastTime = true)
            msg = (mach_msg_header_t *)msg_buffer;
            // 如果有 source1 消息,就转到 label handle_msg 执行,接下来的方法就不会执行了
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                goto handle_msg;
            }
        }

        // 刚才没有 source1 消息要处理
        didDispatchPortLastTime = false;
        // 11. 通知休眠,并且休眠
	    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	    __CFRunLoopSetSleeping(rl);
	    // 休眠中,直到被唤醒
        msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);        
        
        // 12. 被唤醒了,先处理状态,再处理唤醒runloop的消息
        __CFRunLoopSetIgnoreWakeUps(rl);
	    __CFRunLoopUnsetSleeping(rl);
	    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        // 14. 处理唤醒runloop的消息
        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        if (MACH_PORT_NULL == livePort) { 
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
        } else if (livePort == rl->_wakeUpPort) { // 应该是直接调用了runloop的wakeup
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
        } else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 被 timer 唤醒
            CFRUNLOOP_WAKEUP_FOR_TIMER();
        } else if (livePort == dispatchPort) { // 被GCD的主线程port唤醒
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else { // 被其他port唤醒
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
		        mach_msg_header_t *reply = NULL;
		        sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		        if (NULL != reply) {
		            (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		            CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
		        }
	        }
        } 
        // 15. 处理 block
	    __CFRunLoopDoBlocks(rl, rlm);

    // 16. 根据执行结果和参数,决定是否要退出循环    

    return retVal;
}

为什么只有主线程&主队列&common mode时,dispatchPort 有值?

可以用 GCD 派发 block 到主队列和子队列,再在 block 中打上断点,再查看调用栈就可以发现:

派发到主队列时,是通过 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ 函数触发的 block,也就是说,是通过 runloop 完成执行的。

派发到子队列时,是通过 GCD 自己完成的,调用栈里的方法都是 GCD 的方法。

而 dispatchPort 就是专门针对 GCD 派发到主队列的情况的。

为什么一次循环中调用那么多次__CFRunLoopDoBlocks,它怎么那么牛逼?是GCD给的block吗?是performSelector给的block吗?

这个是最迷惑我的。

performSelector 的 block 是通过 source0/timer/直接调用 完成的。

GCD 如上面所说。

那 __CFRunLoopDoBlocks 中执行的 block 怎么添加上的呢?

后来我意外发现 layoutSubviews() 是通过 __CFRunLoopDoBlocks 调用的。所以我推测:

  1. 在一次 runloop 循环的某个时机,检查是否需要刷新 layout
  2. 结果是需要的话,就把包裹的有 layoutSubviews 方法的 block 加入到 block 队列中
  3. 等到某个时机,再处理 block 队列,这个时机可能是 source0 执行完,可能是 source1 执行后 等

所以一次循环中会执行那么多次 __CFRunLoopDoBlocks

所以会在执行 block 队列前判断 sourceHandledThisLoop

poll 是要退出循环的意思吗?为什么 poll == true 时,不通知观察者即将进入/退出休眠

poll 是轮询的意思(这里这样翻译不知道对不对)

poll == true:

在这次循环中处理了 source0,或者 超时时间是 0

poll == false:

在这次循环中没有处理到 source0,并且超时时间不是 0

如果 poll == true,不会通知观察者即将进入/退出休眠,因为它不会休眠,它不会让线程去陷入到 mach_msg 调用中

如果 poll == false,会通知观察者即将进入/退出休眠,线程会陷入到 mach_msg 调用中

超时时间的好理解,但是 source0 的原因,我不知道为什么?

为什么这次循环中处理了 source0,就不用去监听 source1 了,直接继续下一次循环 或者 退出循环(stopAfterHandle == true 的情况下)

一次循环只处理一次 livePort,万一有多个事件到来怎么办?

应该不会多个事件把它唤醒,一个事件唤醒它,它就醒了,不积攒。

即使有多个也没关系,它会继续循环,会接着处理的,在休眠之前都会处理完的。

runloop 的应用

一开始我纯读源码,很难理解,后来看了 runloop 应用之后,很多问题都迎刃而解了。

切换 runloop mode

有两种方式:

  1. 每次进入 runloop mode 的时间不定 + 切换 mode 时,主动 stop runloop
- (void)switchModeInSubThread {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
	    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:UITrackingRunLoopMode];
        self.curMode = NSDefaultRunLoopMode;
        self.loop = CFRunLoopGetCurrent();
        CFRetain(self.loop);
        [self addObserverToLoop];
        do {
            [[NSRunLoop currentRunLoop] runMode:self.curMode beforeDate:[NSDate distantFuture]];
        } while (1);
    });
}

- (void)changeToDefault {
    self.curMode = NSDefaultRunLoopMode;
    CFRunLoopStop(self.loop);
}

- (void)changeToTracking {
    self.curMode = UITrackingRunLoopMode;
    CFRunLoopStop(self.loop);
}
  1. 每次进入 runloop mode 只执行一次循环 + 不需要 stop runloop
- (void)switchModeInSubThread {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
	    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:UITrackingRunLoopMode];
        self.curMode = NSDefaultRunLoopMode;
        self.loop = CFRunLoopGetCurrent();
        CFRetain(self.loop);
        [self addObserverToLoop];
        do {
            [[NSRunLoop currentRunLoop] runMode:self.curMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
        } while (1);
    });
}
- (void)changeToDefault {
    self.curMode = NSDefaultRunLoopMode;
}

- (void)changeToTracking {
    self.curMode = UITrackingRunLoopMode;
}

调用方法

+ (void)test {
    customRunloop = [[CustomRunloop alloc] init];
    [customRunloop switchModeInSubThread];
    dispatch_after(1.0, dispatch_get_main_queue(), ^{    
        [[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            if ([customRunloop.curMode isEqualToString:NSDefaultRunLoopMode]) {
                [customRunloop changeToTracking];
            } else {
                [customRunloop changeToDefault];
            }
        }] fire];
    });

NSRunLoop

// 如果没有 loop 在循环,会执行 default mode loop,如果 default mode loop 正在循环,会修改当前循环的结束时间
// stopAfterHandle = true
- (void)runUntilDate:(NSDate *)limitDate;
// 与上一个相似,但是 mode 由参数指定。如果正在循环的 mode 与指定的 mode 不一样,那么调用无效
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
- (void)testGCDMainQueue {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"main queue task 1");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"main queue task 2");
        });
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        NSLog(@"main queue task 3");
    });
    /// 输出 1,3,2
    
    CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
        NSLog(@"main queue task 4");
        CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
            NSLog(@"main queue task 5");
        });
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        NSLog(@"main queue task 6");
    });
    /// 输出 4,5,6
}