Runloop原理分析

一般來講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線程能隨時(shí)處理事件但并不退出,通常的代碼邏輯是這樣的:

do {
   //獲取消息
   //處理消息
} while (消息 != 退出)

這種模型通常被稱作 Event Loop 。 實(shí)現(xiàn)這種模型的關(guān)鍵點(diǎn)在于:如何管理事件/消息,如何讓線程在沒有處理消息時(shí)休眠以避免資源占用、在有消息到來時(shí)立刻被喚醒。

所以,RunLoop 實(shí)際上就是一個(gè)對象,這個(gè)對象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來執(zhí)行上面 Event Loop 的邏輯。線程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 “接受消息->等待->處理” 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。

以iOS 程序的 main 函數(shù)為例:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main start");
        int re = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        NSLog(@"main stop");
        return re;
    }
}

main 函數(shù)是程序的入口,那么為什么程序執(zhí)行完畢后沒有退出呢?因?yàn)?RunLoop,使線程循環(huán),能夠隨時(shí)處理事件但并不退出。上面代碼中 UIApplicationMain()方法在這里不僅完成了初始化我們的程序并設(shè)置程序 Delegate 的任務(wù),而且隨之開啟了主線程的 RunLoop,開始接受處理事件。這樣我們的應(yīng)用就可以在無人操作的時(shí)候休息,需要讓它干活的時(shí)候又能立馬響應(yīng)。

OSX/iOS 系統(tǒng)中,提供了兩個(gè)這樣的對象: NSRunLoopCFRunLoopRef。
CFRunLoopRef是在 CoreFoundation`` 框架內(nèi)的,它提供了純 C 函數(shù)的 API,所有這些 API 都是線程安全的。NSRunLoop是基于CFRunLoopRef``` 的封裝,提供了面向?qū)ο蟮?API,但是這些 API 不是線程安全的。

CFRunLoopRef的代碼是開源的,可以點(diǎn)擊下載到整個(gè) CoreFoundation 的源碼來查看。http://opensource.apple.com/tarballs/CF/

RunLoop大致內(nèi)部結(jié)構(gòu):

struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};

RunLoop 機(jī)制關(guān)系圖總覽:


RunLoop與線程的關(guān)系

蘋果不允許直接創(chuàng)建 RunLoop,提供了兩個(gè)獲取函數(shù),CFRunLoopRef 的獲取方法為CFRunLoopGetMain(), CFRunLoopGetCurrent()。NSRunLoop 的獲取方法是 currentRunLoop,mainRunLoop。NSRunLoop 對象可以通過 getCFRunLoop 方法獲得 CFRunLoopRef 對象。CFRunLoopRef 的內(nèi)部邏輯如下:

/// 全局的 Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問 loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;

/// 獲取一個(gè) pthread 對應(yīng)的 Run Loop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);

    if (!loopsDic) {
        // 第一次進(jìn)入時(shí),初始化全局Dic,并先為主線程創(chuàng)建一個(gè) Run Loop。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }

    /// 直接從 Dictionary 里獲取。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));

    if (!loop) {
        /// 取不到時(shí),創(chuàng)建一個(gè)
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        /// 注冊一個(gè)回調(diào),當(dāng)線程銷毀時(shí),順便也銷毀其對應(yīng)的 Run Loop。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }

    OSSpinLockUnLock(&loopsLock);
    return loop;
}

CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}

CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

從上面的代碼可以看出,線程和 RunLoop 之間是一一對應(yīng)的(這也就解釋了前面關(guān)系圖中 CFRunLoopThread 連線中的兩個(gè)1的意義),其關(guān)系是保存在一個(gè)全局的 Dictionary 里。線程剛創(chuàng)建時(shí)并沒有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有,所以一個(gè)子線程,你想要它有 RunLoop 就必須在該線程內(nèi)調(diào)用 NSRunLoop *runLoop =[NSRunLoop currentRunLoop]。如果你想啟動(dòng)這個(gè) RunLoop,則要繼續(xù)調(diào)用[runLoop run]。但是注意,一般不需要開啟子線程的 runLoop,因?yàn)檫@會(huì)讓子線程一直存在,不會(huì)回收。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)。

RunLoop主要組成

CoreFoundation 里面關(guān)于 RunLoop 有5個(gè)類:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef
    他們之間的關(guān)系如下圖:

    一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode,這個(gè)Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
CFRunLoopMode

CFRunLoopMode 結(jié)構(gòu)大致如下:

struct __CFRunLoopMode {
CFStringRef _name; // mode名稱
CFMutableSetRef _sources0; // sources0
CFMutableSetRef _sources1; // sources1
CFMutableArrayRef _observers; // 通知
CFMutableArrayRef _timers; // 定時(shí)器
__CFPortSet _portSet; // 保存所有需要監(jiān)聽的port,比如 _wakeUpPort,_timerPort都保存在這個(gè)數(shù)組中
};

一個(gè)CFRunLoopMode對象有一個(gè)name,若干source0、source1、timer、observer和若干port,事件都是由Mode在管理,而RunLoop管理Mode。

Runloop中有個(gè)概念叫 CommonModes:一個(gè) Mode 可以將自己標(biāo)記為Common屬性(通過將其 ModeName 添加到 RunLoopcommonModes 中)。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時(shí),RunLoop 都會(huì)自動(dòng)將 _commonModeItems里的Source/Observer/Timer 同步到具有 Common 標(biāo)記的所有Mode里。

應(yīng)用場景舉例:主線程的 RunLoop 里有兩個(gè)預(yù)置的 ModekCFRunLoopDefaultModeUITrackingRunLoopMode。這兩個(gè) Mode都已經(jīng)被標(biāo)記為Common屬性。DefaultMode 是 App 平時(shí)所處的狀態(tài),TrackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)。當(dāng)你創(chuàng)建一個(gè)Timer 并加到DefaultMode 時(shí),Timer 會(huì)得到重復(fù)回調(diào),但此時(shí)滑動(dòng)一個(gè)TableView時(shí),RunLoop 會(huì)將 mode 切換為 TrackingRunLoopMode,這時(shí) Timer就不會(huì)被回調(diào),并且也不會(huì)影響到滑動(dòng)操作。

如果需要一個(gè)Timer,在兩個(gè) Mode中都能得到回調(diào),一種辦法就是將這個(gè)Timer分別加入這兩個(gè)Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoopcommonModeItems 中。commonModeItemsRunLoop 自動(dòng)更新到所有具有Common屬性的 Mode 里去。

CFRunLoopSource

Source0
處理App內(nèi)部事件、App自己負(fù)責(zé)管理(觸發(fā)),如UIEvent。source0是非基于Port的。只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件。使用時(shí),你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè)Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop)來喚醒 RunLoop,讓其處理這個(gè)事件。

Source1 :由RunLoop和內(nèi)核管理,Mach port驅(qū)動(dòng),如CFMachPortCFMessagePort。
包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動(dòng)喚醒 RunLoop 的線程。

CFRunLoopTimer

是基于時(shí)間的觸發(fā)器,基本上說的就是NSTimer,它受RunLoopMode影響,當(dāng)其加入到 RunLoop 時(shí),RunLoop會(huì)注冊對應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)。如果線程阻塞或者不在這個(gè)Mode下,觸發(fā)點(diǎn)將不會(huì)執(zhí)行,一直等到下一個(gè)周期時(shí)間點(diǎn)觸發(fā)。

CFRunLoopObserver

CFRunLoopObserverRef是觀察者,每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過回調(diào)接受到這個(gè)變化??梢杂^測的時(shí)間點(diǎn)有以下幾個(gè):

kCFRunLoopEntry = (1 << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1 << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1 << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1 << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1 << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1 << 7), // 即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 包含上面所有狀態(tài)

RunLoop 的內(nèi)部邏輯

RunLoop 內(nèi)部的邏輯大致如下:


其內(nèi)部代碼整理如下:

/// 用DefaultMode啟動(dòng)
void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}

/// 用指定的Mode啟動(dòng),允許設(shè)置RunLoop超時(shí)時(shí)間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

/// RunLoop的實(shí)現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

    /// 首先根據(jù)modeName找到對應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    /// 如果mode里沒有source/timer, 直接返回。
    if (__CFRunLoopModeIsEmpty(currentMode)) return;

    /// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

    /// 內(nèi)部函數(shù),進(jìn)入loop
    __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {

        Boolean sourceHandledThisLoop = NO;
        int retVal = 0;
        do {

            /// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
            /// 3. 通知 Observers: RunLoop 即將觸發(fā) Source 回調(diào)。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
            /// 執(zhí)行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);

            /// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
            sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
            /// 執(zhí)行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);

            /// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。
            if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) goto handle_msg;
            }

            /// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
            if (!sourceHandledThisLoop) {
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
            }

            /// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
            /// ? 一個(gè)基于 port 的Source 的事件。
            /// ? 一個(gè) Timer 到時(shí)間了
            /// ? RunLoop 自身的超時(shí)時(shí)間到了
            /// ? 被其他什么調(diào)用者手動(dòng)喚醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
            }

            /// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

            /// 收到消息,處理消息。
            handle_msg:

            /// 9.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)。
            if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            } 

            /// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。
            else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 

            /// 9.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件
            else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                if (sourceHandledThisLoop) {
                    mach_msg(reply, MACH_SEND_MSG, reply);
                }
            }

            /// 執(zhí)行加入到Loop的block
            __CFRunLoopDoBlocks(runloop, currentMode);


            if (sourceHandledThisLoop && stopAfterHandle) {
                /// 進(jìn)入loop時(shí)參數(shù)說處理完事件就返回。
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                /// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) {
                /// 被外部調(diào)用者強(qiáng)制停止了
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                /// source/timer一個(gè)都沒有了
                retVal = kCFRunLoopRunFinished;
            }

            /// 如果沒超時(shí),mode里沒空,loop也沒被停止,那繼續(xù)loop。
        } while (retVal == 0);
    }

    /// 10. 通知 Observers: RunLoop 即將退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

可以看到,實(shí)際上 RunLoop 就是這樣一個(gè)函數(shù),其內(nèi)部是一個(gè) do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)里;直到超時(shí)或被手動(dòng)停止,該函數(shù)才會(huì)返回。

應(yīng)用實(shí)例

AutoreleasePool
App啟動(dòng)后,蘋果在主線程 RunLoop 里注冊了兩個(gè) Observer
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池。其 order 是-2147483647,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用_objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被 RunLoop創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏。
PerformSelecter
當(dāng)調(diào)用 NSObjectperformSelecter:afterDelay:后,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒有 RunLoop,則這個(gè)方法會(huì)失效。
當(dāng)調(diào)用 performSelector:onThread: 時(shí),實(shí)際上其會(huì)創(chuàng)建一個(gè) Timer加到對應(yīng)的線程去,同樣的,如果對應(yīng)線程沒有RunLoop 該方法也會(huì)失效。

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

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