iOS RunLoop理解

前言

應(yīng)用在運(yùn)行以后,只要有觸發(fā)事件(點(diǎn)擊按鈕),應(yīng)用程序就會(huì)立刻做出相應(yīng)的反應(yīng),如果不對(duì)它進(jìn)行操作,應(yīng)用程序就像靜止了一樣。給我們的感覺就像應(yīng)用一直處于隨時(shí)待命的狀態(tài),在沒人操作的時(shí)候它一直在休息,在讓它干活的時(shí)候,它就能立刻響應(yīng)。那這種效果在底層是怎樣實(shí)現(xiàn)的呢?

RunLoop定義與作用

定義:在程序運(yùn)行過程中循環(huán)做一些事情。
作用:保持程序的持續(xù)運(yùn)行;處理App中的各種事件(觸摸事件、定時(shí)器事件);節(jié)省CPU資源,提高程序性能:該做事時(shí)做事,該休息時(shí)休息;

RunLoop與線程

每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象;
RunLoop保存在一個(gè)全局的Dictionary里,線程作為key,RunLoop作為value;
線程剛創(chuàng)建時(shí)并沒有RunLoop對(duì)象,RunLoop會(huì)在第一次獲取它時(shí)創(chuàng)建;
RunLoop會(huì)在線程結(jié)束時(shí)銷毀;
主線程的RunLoop已經(jīng)自動(dòng)獲?。▌?chuàng)建),子線程默認(rèn)沒有開啟RunLoop;

底層結(jié)構(gòu)

一個(gè)RunLoop對(duì)象(CFRunLoopRef)中包含若干個(gè)運(yùn)行模式(CFRunLoopModeRef)。而每一個(gè)運(yùn)行模式下又包含若干個(gè)輸入源(CFRunLoopSourceRef)、定時(shí)源(CFRunLoopTimerRef)、觀察者(CFRunLoopObserverRef)。

截屏2021-10-09 上午9.50.26.png
struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
};


typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的運(yùn)行模式;
一個(gè)RunLoop包含若干個(gè)mode,每個(gè)mode又包含若干個(gè)Source0/Source1/Time/Observer;
RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode;
如果需要切換Mode,只能切換當(dāng)前的Mode,再重新選擇一個(gè)Mode進(jìn)入;
不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響;

CFRunLoopModeRef

kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行;
UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響;
kCFRunLoopCommonModes:偽模式,不是一種真正的運(yùn)行模式;

Source0 : 觸摸事件處理 ,performSelector:onThread:

Source1 : 基于Port的線程間通信 , 系統(tǒng)事件捕捉

Times : NSTimer , performSelector:withObject:afterDelay:

Observers : 用于監(jiān)聽RunLoop的狀態(tài) ; UI刷新(BeforeWaiting) ; Autorelease pool(BeforeWaiting)

源碼分析

創(chuàng)建iOS項(xiàng)目,在- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event方法打斷點(diǎn),點(diǎn)擊手機(jī)屏幕進(jìn)入斷點(diǎn),在控制臺(tái)輸入bt指令,可以看到RunLoop的調(diào)用流程,RunLoop的入口為:CFRunLoopRunSpecific函數(shù)。

截屏2021-10-11 上午10.06.37.png

C語言代碼比較難理解,源碼進(jìn)過刪減如下:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        //通知觀察者進(jìn)入RunLoop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        //創(chuàng)建并啟動(dòng)RunLoop:去處理需要做的事情
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        //通知觀察者退出RunLoop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        //通知Observer即將處理Timers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //通知Observer即將處理Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //處理Block
        __CFRunLoopDoBlocks(rl, rlm);
        //處理Sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            //處理Block
            __CFRunLoopDoBlocks(rl, rlm);
    }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        //判斷有無Sources1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            //如果有Sources1,就跳轉(zhuǎn)到handle_msg
            goto handle_msg;
        }


        didDispatchPortLastTime = false;
    //通知Observer即將處理休眠
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    //進(jìn)入休眠
    __CFRunLoopSetSleeping(rl);
        do {
         
            //循環(huán)等待新的消息來喚醒當(dāng)前線程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
        } while (1);

        // user callouts now OK again
    __CFRunLoopUnsetSleeping(rl);
        //通知Observer結(jié)束休眠
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        //被Timers喚醒,處理Timers事件
       if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        //被Timers喚醒,處理Timers事件
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        //被GCD喚醒,處理Timers事件
        else if (livePort == dispatchPort) {
            //處理GCD事件
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
    
        } else {
            //被Source1喚醒,處理Source1事件
        sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;

        }
     //處理Block
    __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;
    }
        
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);

    } while (0 == retVal);

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

    return retVal;
}

由源碼__CFRunLoopRun函數(shù)中我們可以發(fā)現(xiàn),RunLoop的本質(zhì)就是一個(gè)循環(huán)(do .... while)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • RunLoop概念 一個(gè)APP之所以能在程序運(yùn)行起來不停止,就是RunLoop的原因,RunLoop就像一個(gè)死循環(huán)...
    宙斯YY閱讀 508評(píng)論 0 2
  • RunLoop 是 iOS 和 OSX 開發(fā)中非?;A(chǔ)的一個(gè)概念,這篇文章將從 CFRunLoop 的源碼入手,介...
    柳大官人閱讀 421評(píng)論 0 3
  • iOS刨根問底-深入理解RunLoop 2017-05-08 10:35 by KenshinCui 概述 Run...
    mengjz閱讀 1,645評(píng)論 1 10
  • 序言 RunLoop 是 iOS 和 OSX 開發(fā)中非常基礎(chǔ)的一個(gè)概念,該文章將從CFRunLoop 的源碼入手,...
    路飛_Luck閱讀 655評(píng)論 3 4
  • 零:前言 聲明:本文非原創(chuàng),是我在整理自己iOS知識(shí)體系時(shí),閱讀到這篇文章,感覺作者整理的非常好,就轉(zhuǎn)載到這里方便...
    苦笑男神閱讀 953評(píng)論 0 6

友情鏈接更多精彩內(nèi)容