iOS RunLoop理解

前言

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

RunLoop定義與作用

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

RunLoop與線程

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

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

一個RunLoop對象(CFRunLoopRef)中包含若干個運行模式(CFRunLoopModeRef)。而每一個運行模式下又包含若干個輸入源(CFRunLoopSourceRef)、定時源(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的運行模式;
一個RunLoop包含若干個mode,每個mode又包含若干個Source0/Source1/Time/Observer;
RunLoop啟動時只能選擇其中一個Mode,作為currentMode;
如果需要切換Mode,只能切換當(dāng)前的Mode,再重新選擇一個Mode進入;
不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響;

CFRunLoopModeRef

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

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項目,在- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event方法打斷點,點擊手機屏幕進入斷點,在控制臺輸入bt指令,可以看到RunLoop的調(diào)用流程,RunLoop的入口為:CFRunLoopRunSpecific函數(shù)。

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

C語言代碼比較難理解,源碼進過刪減如下:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        //通知觀察者進入RunLoop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        //創(chuà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);
    //進入休眠
    __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ì)就是一個循環(huán)(do .... while)。

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

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

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