Runloop學(xué)習(xí)筆記

1、什么是Runloop?Runloop線程什么關(guān)系?

直接參考官方文檔
Runloop跟線程是一一對應(yīng)的關(guān)系,底層通過一個哈希表來進(jìn)行映射存儲,線程為key,runloop為value。主線程的Runloop在app啟動時自動創(chuàng)建,然后存到哈希表中。子線程在我們需要的時候系統(tǒng)會幫我們創(chuàng)建,存入表中,下次直接讀取,保證一個線程只有一個runloop。

2、Runloop解剖

2.1、Runloop底層結(jié)構(gòu)

2.1.1、整體結(jié)構(gòu)概覽

運(yùn)行循環(huán)(Runloop)從兩種不同類型的源(Source)接收事件(Event)。輸入源(Input source)傳遞異步事件,通常是來自另一個線程或不同應(yīng)用程序的消息。定時器源(Timer sources)提供同步事件,這些事件以預(yù)定的時間或重復(fù)的間隔發(fā)生。兩種類型的源都使用應(yīng)用程序特定的處理例程在事件到達(dá)時進(jìn)行處理。
下圖顯示了運(yùn)行循環(huán)的概念結(jié)構(gòu)和各種來源。輸入源將異步事件傳遞給相應(yīng)的處理程序,并導(dǎo)致runUntilDate:方法(在線程的關(guān)聯(lián)NSRunLoop對象上調(diào)用)退出。計時器源將事件交付給它們的處理例程,但不會導(dǎo)致運(yùn)行循環(huán)退出。


Runloop-來自官方文檔.jpg

除了處理輸入源之外,運(yùn)行循環(huán)還生成關(guān)于運(yùn)行循環(huán)行為的通知。已注冊的運(yùn)行循環(huán)觀察者(Observer)可以接收這些通知,并使用它們在線程上執(zhí)行額外的處理??梢允褂肅ore Foundation在相應(yīng)的線程上注冊Runloop觀察者。

2.1.2、底層數(shù)據(jù)結(jié)構(gòu)
  • Runloop的底層結(jié)構(gòu)__CFRunLoop
    Runloop本身也是個對象,底層結(jié)構(gòu)是個結(jié)構(gòu)體(__CFRunLoop):
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

關(guān)鍵屬性解析:

_pthread:當(dāng)前線程
_commonModes common:模式,是個集合
_currentMode:當(dāng)前運(yùn)行的模式
_modes:所有模式

一個Runloop可以關(guān)聯(lián)多個Mode,但是每次只能在一個Mode下運(yùn)行。

  • 模式的底層結(jié)構(gòu)__CFRunLoopMode
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

關(guān)鍵屬性解析:

_sources0:非基于Port的輸入源,一般是app內(nèi)部事件源,包括觸摸事件等;
_sources1:基于Port的輸入源,一般是線程間、進(jìn)程間通信的源。
_observers:觀察者,可以監(jiān)聽Runloop的運(yùn)行狀態(tài)
_timers:也叫計時器源

一個Mode可以同時關(guān)聯(lián)多個源,Runloop只有在當(dāng)前模式運(yùn)行時才會執(zhí)行其關(guān)聯(lián)的源傳遞的事件。

  • Runloop整體結(jié)構(gòu)圖


    runloop結(jié)構(gòu).jpg

下面幾節(jié)提供有關(guān)運(yùn)行循環(huán)的組件及其運(yùn)行模式(Mode)的更多信息。它們還描述了事件處理期間在不同時間生成的通知。

2.2、模式(Run Loop Modes)

Mode是要監(jiān)聽的輸入源和計時器的集合,以及要通知的運(yùn)行循環(huán)觀察者的集合。每次運(yùn)行Runloop時,都要指定(顯式或隱式)要運(yùn)行的特定“模式”。在運(yùn)行循環(huán)的該過程中,只有與該模式相關(guān)聯(lián)的源被監(jiān)聽并允許交付其事件(類似地,只有與該模式相關(guān)聯(lián)的觀察者才會被通知運(yùn)行循環(huán)的進(jìn)程)。與其他模式關(guān)聯(lián)的源將保留新加入的事件,直到后續(xù)事件以相應(yīng)的模式通過循環(huán)。

模式實際上就是一個字符串名稱。我們可以自定義。系統(tǒng)同為我們定義了一些模式:

NSDefaultRunLoopMode (Cocoa)
kCFRunLoopDefaultMode (Core Foundation)
NSEventTrackingRunLoopMode (Cocoa)
NSRunLoopCommonModes (Cocoa)
kCFRunLoopCommonModes (Core Foundation)
NSConnectionReplyMode (Cocoa)
NSModalPanelRunLoopMode (Cocoa)

我們常用的是Default和Tracking(scrollview滾動時),Common是一組常用模式的可配置組。將輸入源與此模式關(guān)聯(lián)也將其與組中的每個模式關(guān)聯(lián)。對于Cocoa應(yīng)用程序,該集合默認(rèn)包括默認(rèn)、模式和事件跟蹤模式。

備注:模式區(qū)分基于事件的來源,而不是事件的類型。

2.3、輸入源(Input Sources)

輸入源將事件異步地交付給線程。事件的源取決于輸入源的類型,通常是兩種類型。其一是基于端口(Port-Based Sources)的輸入源,監(jiān)聽?wèi)?yīng)用程序的Mach端口;其二自定義輸入源監(jiān)聽自定義事件源。就您的Runloop而言,輸入源是基于端口的還是自定義的并不重要。系統(tǒng)通常都實現(xiàn)兩種類型的輸入源,您可以按原樣使用它們。這兩個信號源之間的唯一區(qū)別是它們發(fā)出信號的方式。基于端口的源由內(nèi)核自動發(fā)出信號,而自定義源必須從另一個線程手動發(fā)出信號。

當(dāng)你創(chuàng)建一個輸入源時,你可以將它分配給運(yùn)行循環(huán)的一個或多個模式。模式影響在任何給定時刻監(jiān)聽的輸入源。大多數(shù)情況下,在默認(rèn)模式下運(yùn)行Runloop,但您也可以指定自定義模式。如果輸入源不在當(dāng)前監(jiān)視的模式中,則它生成的任何事件都將被保存,直到運(yùn)行循環(huán)以正確的模式運(yùn)行為止。

以下部分將描述一些輸入源。

2.3.1、基于端口的源(Port-Based Sources)

Cocoa和Core Foundation為使用與端口相關(guān)的對象和函數(shù)創(chuàng)建基于端口的輸入源提供了內(nèi)置支持。例如,在Cocoa中,你根本不需要直接創(chuàng)建輸入源。你只需創(chuàng)建一個端口對象,并使用NSPort的方法將該端口添加到Runloop中。端口對象為你處理所需輸入源的創(chuàng)建和配置。

在Core Foundation中,必須手動創(chuàng)建端口及其運(yùn)行循環(huán)源。在這兩種情況下,要使用與端口不透明類型(CFMachPortRef、CFMessagePortRef或CFSocketRef)相關(guān)聯(lián)的函數(shù)來創(chuàng)建適當(dāng)?shù)膶ο蟆?/p>

有關(guān)如何設(shè)置和配置基于端口的自定義源的示例,參考如下代碼:

     self.workThread = [[NSThread alloc] initWithBlock:^{
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        NSPort *port = [[NSPort alloc] init];
        [runloop addPort:port forMode:NSRunLoopCommonModes];
        [runloop run];
    }];
    [self.workThread start];
2.3.2、自定義源(Custom Input Sources)

要創(chuàng)建自定義輸入源,必須使用Core Foundation中與CFRunLoopSourceRef 不透明類型關(guān)聯(lián)的函數(shù)。你可以使用多個回調(diào)函數(shù)配置自定義輸入源。Core Foundation在不同的點(diǎn)調(diào)用這些函數(shù)來配置源,處理任何傳入的事件,并在從運(yùn)行循環(huán)中刪除源時銷毀源。

除了定義事件到達(dá)時自定義源的行為外,還必須定義事件傳遞機(jī)制。源的這一部分在一個單獨(dú)的線程上運(yùn)行,負(fù)責(zé)向輸入源提供它的數(shù)據(jù),并在數(shù)據(jù)準(zhǔn)備好進(jìn)行處理時向它發(fā)出信號。事件交付機(jī)制由您決定,但不必過于復(fù)雜。

有關(guān)如何創(chuàng)建自定義輸入源的示例,請參考如下代碼:

    CFRunLoopSourceContext context = {
        0,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        schedule,
        cancel,
        perform,
    };
    /**
     
     參數(shù)一:傳遞NULL或kCFAllocatorDefault以使用當(dāng)前默認(rèn)分配器。
     參數(shù)二:優(yōu)先級索引,指示處理運(yùn)行循環(huán)源的順序。這里我傳0為了的就是自主回調(diào)
     參數(shù)三:為運(yùn)行循環(huán)源保存上下文信息的結(jié)構(gòu)
     */
    CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
    CFRunLoopRef rlp = CFRunLoopGetCurrent();
    // source --> runloop 指定了mode  那么此時我們source就進(jìn)入待緒狀態(tài)
    CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode);
    // 一個執(zhí)行信號
    CFRunLoopSourceSignal(source0);
    // 喚醒 run loop 防止沉睡狀態(tài)
    CFRunLoopWakeUp(rlp);
    // 取消 移除
    CFRunLoopRemoveSource(rlp, source0, kCFRunLoopDefaultMode);
    CFRelease(rlp);

void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"準(zhǔn)備代發(fā)");
}

void perform(void *info){
    NSLog(@"執(zhí)行");
}

void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"取消了");
}

2.3.3、基于performSelector:的源(Cocoa Perform Selector Sources)

除了基于端口的源之外,Cocoa還定義了一個自定義輸入源,允許你在任何線程上執(zhí)行選擇器。與基于端口的源類似,執(zhí)行選擇器請求在目標(biāo)線程上序列化,這減輕了在一個線程上運(yùn)行多個方法時可能出現(xiàn)的許多同步問題。與基于端口的源不同,執(zhí)行選擇器源在執(zhí)行選擇器之后將自己從運(yùn)行循環(huán)中移除。

相關(guān)方法:

performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

2.4、計時器源(Timer Sources)

計時器源在未來的預(yù)設(shè)時間同步地向線程交付事件。計時器是線程通知自己做某事的一種方式。

盡管計時器生成基于時間的通知,但它不是一種實時機(jī)制。像輸入源一樣,計時器與運(yùn)行循環(huán)的特定模式相關(guān)聯(lián)。如果一個計時器不在當(dāng)前被運(yùn)行循環(huán)監(jiān)視的模式中,那么它不會被觸發(fā),直到你在一個計時器支持的模式中運(yùn)行運(yùn)行循環(huán)。類似地,如果在Runloop正在執(zhí)行一個處理例程時觸發(fā)一個計時器,那么計時器將等待到下一次通過Runloop調(diào)用它的處理例程。如果Runloop根本沒有運(yùn)行,則計時器永遠(yuǎn)不會觸發(fā)。

你可以將計時器配置為只生成一次或重復(fù)事件。重復(fù)計時器根據(jù)預(yù)定的觸發(fā)時間而不是實際的觸發(fā)時間自動重新調(diào)度自己。例如,如果一個計時器被計劃在特定時間觸發(fā),并且之后每隔5秒觸發(fā)一次,那么計劃的觸發(fā)時間將始終落在最初的5秒時間間隔上,即使實際的觸發(fā)時間被延遲了。如果觸發(fā)時間被延遲到錯過了一個或多個預(yù)定的觸發(fā)時間,則在錯過的時間段只觸發(fā)一次計時器。在觸發(fā)錯過的時間段之后,計時器將被重新調(diào)度到下一個預(yù)定的觸發(fā)時間。

有關(guān)配置定時器源的更多信息,請參見配置定時器源。有關(guān)參考如下代碼:

    self.workThread = [[NSThread alloc] initWithBlock:^{
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1  target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        [runloop addTimer:timer forMode:NSRunLoopCommonModes];
        [runloop run];
    }];
    [self.workThread start];

2.5、觀察者(Run Loop Observers)

源在適當(dāng)?shù)漠惒交蛲绞录l(fā)生時觸發(fā),與之相反,運(yùn)行循環(huán)觀察者在運(yùn)行循環(huán)本身執(zhí)行期間在特定位置觸發(fā)。你可以使用運(yùn)行循環(huán)觀察器來準(zhǔn)備線程以處理給定的事件,或者在線程進(jìn)入睡眠狀態(tài)之前準(zhǔn)備線程。你可以將運(yùn)行循環(huán)觀察者與運(yùn)行循環(huán)中的以下事件關(guān)聯(lián)起來:

開始進(jìn)入Runloop(kCFRunLoopEntry)。
當(dāng)Runloop要處理一個計時器時(kCFRunLoopBeforeTimers)。
當(dāng)Runloop要處理輸入源時(kCFRunLoopBeforeSources)。
當(dāng)Runloop即將進(jìn)入睡眠狀態(tài)時(kCFRunLoopBeforeWaiting)。
當(dāng)Runloop已經(jīng)喚醒,但在它處理喚醒它的事件之前(kCFRunLoopAfterWaiting)。
從Runloop中退出(kCFRunLoopExit)。

你可以使用Core Foundation添加Runloop觀察者到應(yīng)用程序中。要創(chuàng)建一個運(yùn)行循環(huán)觀察者,您需要創(chuàng)建一個CFRunLoopObserverRef 不透明類型的新實例。這種類型跟蹤你的自定義回調(diào)函數(shù)和它感興趣的活動。

與計時器類似,運(yùn)行循環(huán)觀察者可以使用一次或多次。一次性觀察者在觸發(fā)后將自己從運(yùn)行循環(huán)中移除,而重復(fù)觀察者則保持連接。你可以指定在創(chuàng)建觀察器時運(yùn)行一次還是重復(fù)。

有關(guān)如何創(chuàng)建運(yùn)行循環(huán)觀察器的示例,請參考一下代碼:

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &mainObserverCallBack, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }

void mainObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    
    ViewController *obj = (__bridge id)info;
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"開始循環(huán)");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"將要執(zhí)行timer");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"將要執(zhí)行輸入源");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"將要進(jìn)入休眠");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"runloop已被喚醒");
            break;
        case kCFRunLoopExit:
            NSLog(@"runloop退出");
            break;
        case kCFRunLoopAllActivities:
            NSLog(@"狀態(tài)更新了");
            break;
        default:
            break;
    }
}

mainObserverCallBack是回調(diào)函數(shù),在回調(diào)函數(shù)可以收到狀態(tài)變化通知。

3、Runloop工作原理

3.1、事件的運(yùn)行循環(huán)序列(The Run Loop Sequence of Events)

每次運(yùn)行它時,線程的運(yùn)行循環(huán)都會處理掛起事件,并為任何附加的觀察者生成通知。它這樣做的順序非常具體,如下圖(參考自網(wǎng)絡(luò)):


Runloop循環(huán).png

1、通知觀察者(Observer)已進(jìn)入運(yùn)行循環(huán)。
2、通知觀察者(Observer)即將被觸發(fā)已經(jīng)就緒的計時器。
3、通知觀察者(Observer)即將被觸發(fā)非基于端口的輸入源。
4、處理非基于端口的輸入源(Source0)。
5、如果有基于端口的輸入源(Source1)已經(jīng)準(zhǔn)備好并等待觸發(fā),則立即處理該事件。執(zhí)行步驟9。
6、通知觀察者(Observer)線程即將進(jìn)入睡眠狀態(tài)。
7、將線程置于睡眠狀態(tài),調(diào)用 mach_msg 等待接受 mach_port 的消息,直到發(fā)生以下事件之一:

  • 基于端口的輸入源(Source1)收到新的事件。
  • 一個計時器的觸發(fā)時間到了。
  • 為Runloop設(shè)置的超時值過期。
  • Runloop被其他調(diào)用者顯式喚醒。

8、通知觀察者(Observer)線程剛被喚醒。
9、處理掛起事件。

  • 如果用戶設(shè)定的計時器觸發(fā)了時間到了,則處理計時器事件并重新啟動循環(huán)。跳轉(zhuǎn)到步驟2。
  • 如果有dispatch到main_queue的block,執(zhí)行block。
  • 如果一個基于端口的輸入源(Source1)被觸發(fā)了,則處理這個事件。
  • 如果Runloop被顯式喚醒,但尚未超時,則重新啟動循環(huán)。跳轉(zhuǎn)轉(zhuǎn)步驟2。

10、通知觀察者(Observer)Runloop退出。

源碼解析

  • Runloop相關(guān)狀態(tài)狀態(tài)
/* Reasons for CFRunLoopRunInMode() to Return */
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4
};

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

  • void CFRunLoopRun(void)
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  • CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    //根據(jù)modeName找到本次運(yùn)行的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //如果沒找到 || mode中沒有注冊任何事件,則就此停止,不進(jìn)入循環(huán)
    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);
    //取上一次運(yùn)行的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    //如果本次mode和上次的mode一致
    rl->_currentMode = currentMode;
    //初始化一個result為kCFRunLoopRunFinished
    int32_t result = kCFRunLoopRunFinished;
    
    if (currentMode->_observerMask & kCFRunLoopEntry )
        /// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit )
        /// 10. 通知 Observers: RunLoop 即將退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}
  • __CFRunLoopRun
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    //獲取系統(tǒng)啟動后的CPU運(yùn)行時間,用于控制超時時間
    uint64_t startTSR = mach_absolute_time();
    
    // 判斷當(dāng)前runloop的狀態(tài)是否關(guān)閉
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    
    //mach端口,在內(nèi)核中,消息在端口之間傳遞。 初始為0
    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)));
    //如果在主線程 && runloop是主線程的runloop && 該mode是commonMode,則給mach端口賦值為主線程收發(fā)消息的端口
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        //mode賦值為dispatch端口_dispatch_runloop_root_queue_perform_4CF
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if (!modeQueuePort) {
            CRASH("Unable to get port for run loop mode queue (%d)", -1);
        }
    }
#endif
    
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
        // 1.0e10 == 1* 10^10
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
        //seconds為超時時間,超時時執(zhí)行__CFRunLoopTimeout函數(shù)
        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
        //永不超時 - 永動機(jī)
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    
    //標(biāo)志位默認(rèn)為true
    Boolean didDispatchPortLastTime = true;
    //記錄最后runloop狀態(tài),用于return
    int32_t retVal = 0;
    
    // itmes
 
    do {
        //初始化一個存放內(nèi)核消息的緩沖池
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
        HANDLE livePort = NULL;
        Boolean windowsMessageReceived = false;
#endif
        //取所有需要監(jiān)聽的port
        __CFPortSet waitSet = rlm->_portSet;
        
        //設(shè)置RunLoop為可以被喚醒狀態(tài)
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        
        /// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
            /// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        /// 執(zhí)行被加入的block
        __CFRunLoopDoBlocks(rl, rlm);
        /// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            /// 執(zhí)行被加入的block
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        //如果沒有Sources0事件處理 并且 沒有超時,poll為false
        //如果有Sources0事件處理 或者 超時,poll都為true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        //第一次do..whil循環(huán)不會走該分支,因為didDispatchPortLastTime初始化是true
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            //從緩沖區(qū)讀取消息
            msg = (mach_msg_header_t *)msg_buffer;
            /// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息。
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                //如果接收到了消息的話,前往第9步開始處理msg
                goto handle_msg;
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }
#endif
        }
        
        didDispatchPortLastTime = false;
        /// 6.通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        //設(shè)置RunLoop為休眠狀態(tài)
        __CFRunLoopSetSleeping(rl);
        // do not do any user callouts after this point (after notifying of sleeping)
        
        // Must push 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.
        
        __CFPortSetInsert(dispatchPort, waitSet);
        
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        
        //這里有個內(nèi)循環(huán),用于接收等待端口的消息
        //進(jìn)入此循環(huán)后,線程進(jìn)入休眠,直到收到新消息才跳出該循環(huán),繼續(xù)執(zhí)行run loop
        do {
            if (kCFUseCollectableAllocator) {
                objc_clear_stack(0);
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            
            msg = (mach_msg_header_t *)msg_buffer;
            //7.接收waitSet端口的消息
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
            //收到消息之后,livePort的值為msg->msgh_local_port,
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);
#else
        if (kCFUseCollectableAllocator) {
            objc_clear_stack(0);
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        /// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個事件喚醒。
        /// ? 一個基于 port 的Source 的事件。
        /// ? 一個 Timer 到時間了
        /// ? RunLoop 自身的超時時間到了
        /// ? 被其他什么調(diào)用者手動喚醒
        
        // mach 事務(wù) - 指令 
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
        
        
#elif DEPLOYMENT_TARGET_WINDOWS
        // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
        __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
        
        __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
        //取消runloop的休眠狀態(tài)
        __CFRunLoopUnsetSleeping(rl);
        /// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        /// 收到消息,處理消息。
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        
#if DEPLOYMENT_TARGET_WINDOWS
        if (windowsMessageReceived) {
            // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            if (rlm->_msgPump) {
                rlm->_msgPump();
            } else {
                MSG msg;
                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            
            // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
            // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
            // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
            __CFRunLoopSetSleeping(rl);
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            __CFRunLoopUnsetSleeping(rl);
            // If we have a new live port then it will be handled below as normal
        }
        
        
#endif
        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
#if DEPLOYMENT_TARGET_WINDOWS
            // Always reset the wake up port, or risk spinning forever
            ResetEvent(rl->_wakeUpPort);
#endif
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            /// 9.1 如果一個 Timer 到時間了,觸發(fā)這個Timer的回調(diào)。
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        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);
            }
        }
#endif
        /// 9.2 如果有dispatch到main_queue的block,執(zhí)行block
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
            void *msg = 0;
#endif
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            /// 9.3 如果一個 Source1 (基于port) 發(fā)出事件了,處理這個事件
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                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);
                }
#elif DEPLOYMENT_TARGET_WINDOWS
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
            }
        }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        
        /// 執(zhí)行加入到Loop的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            /// 進(jìn)入loop時參數(shù)說處理完事件就返回。
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /// 超出傳入?yún)?shù)標(biāo)記的超時時間了
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            /// 被外部調(diào)用者強(qiáng)制停止了
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            /// 自動停止了
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            /// source/timer/observer一個都沒有了
            retVal = kCFRunLoopRunFinished;
        }
        /// 如果沒超時,mode里沒空,loop也沒被停止,那繼續(xù)loop。
    } while (0 == retVal);
    
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    
    return retVal;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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