深刻淺出 RunLoop(三):事件循環機制

RunLoop 系列文章

深刻淺出 RunLoop(一):初識
深刻淺出 RunLoop(二):數據結構
深刻淺出 RunLoop(三):事件循環機制
深刻淺出 RunLoop(四):RunLoop 與線程
深刻淺出 RunLoop(五):RunLoop 與 NSTimer
深刻淺出 RunLoop(六):相關面試題面試

前言

前面咱們介紹了RunLoop的基本概念以及相關數據結構,這篇咱們來說解一下RunLoop究竟是怎麼工做的。數據結構

主線程的 RunLoop 的啓動過程

首先咱們來看一下主線程的RunLoop的啓動過程。
前面咱們說過,咱們的 iOS 程序能保持持續運行的緣由就是在main()函數中調用了UIApplicationMain函數,這個函數內部會啓動主線程的RunLoop
打斷點,經過 LLDB 指令bt查看函數調用棧以下: app

能夠看到,在 UIApplicationMain函數中調用了 Core Foundation 框架下的 CFRunLoopRunSpecific函數。

CFRunLoopRunSpecific 函數實現:RunLoop 的入口

查看源碼中該函數的實現,以下:框架

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // 根據 modeName 找到本次運行的 mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 若是沒找到 || mode 中沒有註冊任何事件,則就此中止,不進入循環
    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;

    // 通知 Observers:即將進入 RunLoop
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // RunLoop 具體要作的事情
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知 Observers:即將退出 RunLoop
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

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

刪掉不重要的細節:async

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知 Observers:即將進入 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // RunLoop 具體要作的事情
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知 Observers:即將退出 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}
複製代碼

從函數調用棧,以及CFRunLoopRunSpecific函數的實現中能夠得知,RunLoop事件循環的實現機制體如今__CFRunLoopRun函數中。 函數

__CFRunLoopRun 函數實現:事件循環的實現機制

因爲該函數實現較複雜,如下爲刪掉細節的精簡版本,想探究具體的能夠查看 Core Foundation 源碼oop

/** * __CFRunLoopRun * * @param rl 運行的 RunLoop 對象 * @param rlm 運行的 mode * @param seconds loop 超時時間 * @param stopAfterHandle true: RunLoop 處理完事件就退出 false:一直運行直到超時或者被手動終止 * @param previousMode 上一次運行的 mode * * @return 返回 4 種狀態 */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) 
{
    int32_t retVal = 0;
    do {
        // 通知 Observers:即將處理 Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知 Observers:即將處理 Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 處理 Blocks
    	__CFRunLoopDoBlocks(rl, rlm);
        // 處理 Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 處理 Blocks
            __CFRunLoopDoBlocks(rl, rlm);
	    }
        // 判斷有無 Source1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // 若是有 Source1,就跳轉到 handle_msg
            goto handle_msg;
        }
        // 通知 Observers:即將進入休眠
	    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    	__CFRunLoopSetSleeping(rl);
        // ⚠️休眠,等待消息來喚醒線程
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    	__CFRunLoopUnsetSleeping(rl);
        // 通知 Observers:剛從休眠中喚醒
	    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

handle_msg:
        if (被 Timer 喚醒) {
            // 處理 Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        } else if (被 GCD 喚醒) {
            // 處理 GCD 
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {  // 被 Source1 喚醒 
            // 處理 Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;  
        }

       // 處理 Blocks
       __CFRunLoopDoBlocks(rl, rlm);
        
       // 設置返回值
	   if (sourceHandledThisLoop && stopAfterHandle) {  // 進入 loop 時參數爲處理完事件就返回
	       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)) {  // mode 中沒有任何的 Source0/Source1/Timer/Observer
	       retVal = kCFRunLoopRunFinished;
	   }
    
    } while (0 == retVal);

    return retVal;
}
複製代碼

從該函數實現中能夠得知RunLoop主要就作如下幾件事情:post

  • __CFRunLoopDoObservers:通知Observers接下來要作什麼
  • __CFRunLoopDoBlocks:處理Blocks
  • __CFRunLoopDoSources0:處理Sources0
  • __CFRunLoopDoSources1:處理Sources1
  • __CFRunLoopDoTimers:處理Timers
  • 處理 GCD 相關:dispatch_async(dispatch_get_main_queue(), ^{ });
  • __CFRunLoopSetSleeping/__CFRunLoopUnsetSleeping:休眠等待/結束休眠
  • __CFRunLoopServiceMachPort -> mach-msg():轉移當前線程的控制權

事件循環機制

__CFRunLoopServiceMachPort 函數實現:RunLoop 休眠的實現原理

__CFRunLoopRun函數中,會調用__CFRunLoopServiceMachPort函數,該函數中調用了mach_msg()函數來轉移當前線程的控制權給內核態/用戶態。ui

  • 沒有消息須要處理時,休眠線程以免資源佔用。調用mach_msg()從用戶態切換到內核態,等待消息;
  • 有消息須要處理時,馬上喚醒線程,調用mach_msg()回到用戶態處理消。

這就是RunLoop休眠的實現原理,也是RunLoop與簡單的do...while循環區別:this

  • RunLoop:休眠的時候,當前線程不會作任何事,CPU 不會再分配資源;
  • 簡單的do...while循環:當前線程並無休息,一直佔用 CPU 資源。
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {		/* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }

        // ⚠️⚠️⚠️
        ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);

        // Take care of all voucher-related work right after mach_msg.
        // If we don't release the previous voucher we're going to leak it.
        voucher_mach_msg_revert(*voucherState);
        
        // Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
        *voucherState = voucher_mach_msg_adopt(msg);
        
        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
                // Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
                // CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
                *voucherCopy = voucher_copy();
            } else {
                *voucherCopy = NULL;
            }
        }

        CFRUNLOOP_WAKEUP(ret);
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        if (MACH_RCV_TOO_LARGE != ret) break;
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}
複製代碼
相關文章
相關標籤/搜索