iOS Runloop底層原理探索

iOS Runloop

[TOC]

官方文檔

1. 什么是Runloop

1.1 Runloop簡(jiǎn)介

Runloop即運(yùn)行循環(huán)。NSRunloopOC Foundation框架中的一個(gè)非常重要的類。

Runloop是事件接收和分發(fā)機(jī)制的一個(gè)實(shí)現(xiàn),是線程相關(guān)的基礎(chǔ)框架的一部分,一個(gè)Runloop就是一個(gè)事件處理的循環(huán)哮肚,用來不停的調(diào)度工作以及處理輸入事件泌辫。這也就是為什么APP放在那里不去動(dòng)它怎棱,過一會(huì)在操作它還是會(huì)給你反饋

Runloop本質(zhì)是一個(gè)do while循環(huán),但是它可以休眠桦踊,有任務(wù)就執(zhí)行,沒任務(wù)就休眠终畅,但是它和普通while循環(huán)還是有區(qū)別的籍胯,普通的while循環(huán)會(huì)導(dǎo)致CPU進(jìn)入忙等待狀態(tài),即一直消耗CPU离福,而Runloop不會(huì)杖狼,Runloop是一種閑等待,即Runloop具備休眠功能妖爷。

1.2 Runloop 的作用

  1. 保持程序的運(yùn)行蝶涩,接受用戶的輸入
  2. 決定程序在何時(shí)處理一些Event(觸摸、定時(shí)器絮识、performSelector)
  3. 調(diào)用解耦(message queue)
  4. 節(jié)省CPU時(shí)間绿聘,沒任務(wù)的時(shí)候休眠,有任務(wù)的時(shí)候處理

1.3 依賴NSRunloop的框架

  • NSTimer
  • UIEvent
  • autorelease
  • NSObject(NSDelaydPerforming)
  • NSObject(NSThreadPerformAddtion)
  • CADisplayLink
  • CATransition
  • CAAnimation
  • dispatch_get_main_queue

1.4 Runloop消息類型

消息類型這里我們就看一下那張經(jīng)典的圖

image
  • port
    監(jiān)聽程序的Mach Ports次舌,是一個(gè)比較底層的東西熄攘,可以簡(jiǎn)單理解為內(nèi)核通過port這種方式將信息發(fā)送,而mach則監(jiān)聽內(nèi)核發(fā)來的port信息彼念,然后將其整理挪圾,打包發(fā)給runloop

  • Customer
    很明顯浅萧,由開發(fā)人員自己發(fā)送。不僅僅是發(fā)送哲思,過程也會(huì)相當(dāng)復(fù)雜惯殊,蘋果也提供了一個(gè)CFRunLoopSource來幫助處理。由于很少用到也殖,可以簡(jiǎn)單說下核心土思,但是對(duì)幫助我們理解runloop卻很有幫助:

    • 定義輸入源(數(shù)據(jù)結(jié)構(gòu))
    • 將輸入源添加到runloop,那么這樣就有了接受者忆嗜,即為R1
    • 協(xié)調(diào)輸入源的客戶端(單獨(dú)線程)己儒,專門監(jiān)聽消息,然后將消息打包成runloop能夠處理的樣式捆毫,即第一步定義的輸入源闪湾。它類似Mach的功能
    • 誰來發(fā)送消息的問題?上面的machport是由內(nèi)核發(fā)送的绩卤。自定義的當(dāng)然要我們自己發(fā)送了途样,首先必須是另一個(gè)線程來發(fā)送(當(dāng)然如果只是測(cè)試的話可以和第三步在同一個(gè)線程),先發(fā)送消息給輸入源濒憋,然后后喚醒R1何暇,因R1一般處于休眠狀態(tài),然后R1根據(jù)輸入源來做相應(yīng)的處理凛驮。
  • Selector Sources:它的事件發(fā)送是同步的裆站,這個(gè)用的比較多

  • Observers:觀察者,首先它并不屬于事件源(不會(huì)影響runloop的生命周期)黔夭,它比較特殊宏胯,用于觀察runloop自身的一些狀態(tài)的,有以下幾種:

    • 進(jìn)入runloop
    • runloop即將執(zhí)行定時(shí)器
    • runloop即將執(zhí)行輸入源(Port本姥,Customer肩袍,Selector Source)
    • runloop即將休眠
    • runloop 被喚醒,在處理完喚醒它的事件之前
    • 退出

2. Runloop 探索

我們知道NSRunloop是對(duì)CFRunloop的封裝婚惫,我們可以下載CFRunloop源碼氛赐,下載地址,中可以下載各個(gè)版本的源碼辰妙。

2.1 Runloop 與線程的關(guān)系

在日常開發(fā)中鹰祸,我們通常使用以下兩種方式獲取Runloop

// 主運(yùn)行循環(huán)
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// 當(dāng)前運(yùn)行循環(huán)
CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();

查看一下源碼:

CFRunLoopGetMain

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    // 主線程
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

CFRunLoopGetCurrent

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    // 調(diào)用_CFRunLoopGet0
    return _CFRunLoopGet0(pthread_self());
}

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    // 如果t為nil,也就是沒有線程密浑,則標(biāo)記為主線程蛙婴,也就是默認(rèn)情況都通過主線程處理
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        
    // 創(chuàng)建全局字典,標(biāo)記為kCFAllocatorSystemDefault
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    // 創(chuàng)建主運(yùn)行循環(huán)
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    // 字典的key-value綁定尔破,這里就是主線程綁定主運(yùn)行循環(huán)
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // 其他線程街图,也就是非主線程的runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
    // 如果沒有獲取到浇衬,就創(chuàng)建一個(gè)新的runloop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        // 將新建的runloop與線程進(jìn)行綁定,也是通過字典的key-value方式
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

根據(jù)以上源碼餐济,我們可以知道Runloop有兩種耘擂,一種是主線程的,另一種是其他線程的絮姆,Runloop與線程在一個(gè)全局字典中是一一對(duì)應(yīng)的醉冤。

2.2 Runloop的創(chuàng)建

根據(jù)上一節(jié)中的代碼我們可以知道,創(chuàng)建Runloop的方法是__CFRunLoopCreate篙悯,下面我們就看看這個(gè)方法:

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    // CFRunLoopRef 類型的 一個(gè) Instance
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    if (NULL == loop) {
    return NULL;
    }
    // 屬性賦值
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}

2.2.1 CFRunLoopRef

通過以上代碼我們可以看到loop是一個(gè)CFRunLoopRef類型蚁阳,源碼中主要是一些賦值操作,下面我們就看看CFRunLoopRef

typedef struct __CFRunLoop * CFRunLoopRef;

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;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

CFRunLoopModeRef

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

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 */
};

從上面的結(jié)構(gòu)體定義中我們可以知道鸽照,一個(gè)Runloop依賴于多個(gè)mode螺捐,意味著一個(gè)Runloop需要處理多個(gè)事物,然后一個(gè)mode有對(duì)應(yīng)著多個(gè)items矮燎,而一個(gè)items中有包含了timer定血、sourceobserver诞外。也就是下面這幅圖:

image

所以Runloop的結(jié)構(gòu)如下:

image

2.2.2 Mode的類型

官方文檔

其中mode在蘋果官方文檔中體積的有五個(gè)澜沟,而在iOS中公開暴露出來的只有NSDefaultRunLoopModeNSRunLoopCommonModesNSRunLoopCommonModes實(shí)際上是一個(gè)mode的集合浅乔,默認(rèn)包含NSDefaultRunLoopModeNSEventTrackingRunLoopMode倔喂。

  1. NSDefaultRunLoopMode:默認(rèn)的mode铝条,一般情況下都是在這個(gè)mode
  2. NSConnectionReplyMode:處理NSConnection對(duì)象相關(guān)事件靖苇,系統(tǒng)內(nèi)部使用,用戶基本不會(huì)使用班缰。
  3. NSModalPanelRunLoopMode:處理modal panels事件贤壁。
  4. NSEventTrackingRunLoopMode:使用這個(gè)Mode去跟著來自用戶交互的事件,比如scrollView的滾動(dòng)
  5. NSRunLoopCommonModes:偽模式埠忘,靈活性更好
image

2.2.3 Source & Timer & Observer

  • Source表示可以喚醒Runloop的一些事件脾拆,例如用戶點(diǎn)擊了屏幕,就會(huì)創(chuàng)建一個(gè)Runloop莹妒,主要分為Source0Source1
    • Source0表示非系統(tǒng)事件名船,即用戶自定義的事件
    • Source1表示系統(tǒng)事件,主要負(fù)責(zé)底層的通訊旨怠,具備喚醒能力
  • Timer就是常用NSTimer定時(shí)器這一類
  • Observer主要用于監(jiān)聽Runloop的狀態(tài)變化渠驼,并作出一定響應(yīng),主要有以下一些狀態(tài)
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    // 進(jìn)入Runloop
    kCFRunLoopEntry = (1UL << 0),
    // 即將處理timers
    kCFRunLoopBeforeTimers = (1UL << 1),
    // 即將處理source
    kCFRunLoopBeforeSources = (1UL << 2),
    // 即將進(jìn)入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),
    // 被喚醒
    kCFRunLoopAfterWaiting = (1UL << 6),
    // 退出Runloop
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

2.2.4 驗(yàn)證Runloop 與 Mode 是一對(duì)多

下面我們通過代碼來驗(yàn)證一下Runloop與Mode的關(guān)系鉴腻。

  • 通過lldb命令獲取mainRunloopcurrentRunloopcurrentMode
po CFRunLoopCopyCurrentMode(mainRunloop)
po CFRunLoopCopyCurrentMode(currentRunloop)
image

從這里可以看到迷扇,runloop在運(yùn)行時(shí)的mode只有一個(gè)百揭。

我們經(jīng)常遇到的一個(gè)經(jīng)典問題,當(dāng)刷新UI的時(shí)候有時(shí)計(jì)時(shí)器是不工作的蜓席,所以就是當(dāng)前modeNSEventTrackingRunLoopMode器一,而不是timer一開始指定的NSDefaultRunLoopMode。所以我們不改變定時(shí)器的運(yùn)行模式厨内,讓他在這個(gè)模式下運(yùn)行timer就不響應(yīng)了祈秕,解決方法就是將定時(shí)器加入到UITrackingRunLoopModeNSRunLoopCommonModes。另外雏胃,還有一種方法就是:將timer加入到頂層的Runloop的commonModeItems中踢步。commonModeItems被Runloop自動(dòng)更新到所有具有common屬性的Mode里去。

下面我們?cè)讷@取一下mainRunloop的所有模型丑掺,使用po CFRunLoopCopyAllModes(mainRunloop)

image

可以看到這里是一個(gè)數(shù)組获印,也就說說runloopCFRunloopMode具有一對(duì)多的關(guān)系。

2.2.5 驗(yàn)證mode 與 item 是一對(duì)多

下面我們?cè)賮眚?yàn)證一下mode和item的關(guān)系街州,添加如下代碼兼丰,添加斷點(diǎn),通過bt命令在lldb中打印堆棧信息

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {

        NSLog(@"99999");
    }];
}

點(diǎn)擊屏幕唆缴,通過bt命令查看堆棧信息:

image

在Runloop源碼中可以查看Item類型鳍征,有以下幾種:

image
  • GCD主隊(duì)列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • observer:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
  • timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • block:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

下面我們通過一個(gè)例子來說明一下,一般初始化timer時(shí)面徽,都會(huì)將timer通過addTimer:forMode:方法添加到runloop中艳丛,于是在源碼中查找addTimer的相關(guān)方法,即CFRunLoopAddTimer方法趟紊,其源碼實(shí)現(xiàn)如下:

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    // 判斷是否是 kCFRunLoopCommonModes 類型
    if (modeName == kCFRunLoopCommonModes) {
        // 是 kCFRunLoopCommonModes 類型
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    if (NULL == rl->_commonModeItems) {
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
        // runloop 與 mode 是一對(duì)多的氮双,mode與item也是一對(duì)多的
    CFSetAddValue(rl->_commonModeItems, rlt);
    if (NULL != set) {
        CFTypeRef context[2] = {rl, rlt};
        /* add new item to all common-modes */
        // 執(zhí)行
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        CFRelease(set);
    }
    } else {
        
        // 如果不是kCFRunLoopCommonModes類型,則查找runloop的類型
    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
    if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
    }
        // 判斷mode是否匹配
    if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
        rlt->_runLoop = rl;
        } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
        return;
        }
        // 如果匹配霎匈,則將runloop加進(jìn)去戴差,而runloop的執(zhí)行依賴于 [runloop run]
        CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
    }
        if (NULL != rlm) {
        __CFRunLoopModeUnlock(rlm);
    }
    }
    __CFRunLoopUnlock(rl);
}

根據(jù)上面的源碼我們可以看到:

  • 首先判斷是否是kCFRunLoopCommonModes類型
  • 是的話就正常處理
  • 不是的話就查找runloop的mode進(jìn)行匹配處理
  • 其中kCFRunLoopCommonModes不是一種模式,是一種抽象的偽模式铛嘱,比defauteMode更加靈活
  • 通過CFSetAddValue(rl->_commonModeItems, rlt);可以得知暖释,runloop與mode是一對(duì)多的關(guān)系,墨吓,同時(shí)也可以得出modeitem也是一對(duì)多的關(guān)系

2.3 Runloop的執(zhí)行

在日常開發(fā)中球匕,我們都知道Runloop的執(zhí)行依賴于run方法,從下面的堆棧信息中可以看出帖烘,其底層執(zhí)行的是__CFRunLoopRun方法亮曹。

image

下面我們看看__CFRunLoopRun的源碼(源碼很多,精簡(jiǎn)的,感興趣的可以下載看一下):

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    ...
    
    do{
        ...
         //通知 Observers: 即將處理timer事件
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //通知 Observers: 即將處理Source事件
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        //處理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        //處理sources0返回為YES
        if (sourceHandledThisLoop) {
            // 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        ...
        
        //如果是timer
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        
        ...
        
        //如果是source1
        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
        }
        ...
    
    }while (0 == retVal);
    
    ...
}

通過源碼我們可以看到乾忱,針對(duì)不同的事件讥珍,有不同的處理:

  • 如有有observer,則調(diào)用__CFRunLoopDoObservers
  • 如果有block窄瘟,則調(diào)用__CFRunLoopDoBlocks
  • 如果有timer衷佃,則調(diào)用__CFRunLoopDoTimers
  • 如果有source0,則調(diào)用__CFRunLoopDoSources0
  • 如果有source1蹄葱,則調(diào)用__CFRunLoopDoSource1

  • 這里我們以timer為例氏义,看看__CFRunLoopDoTimers方法:
// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {  /* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    // 循環(huán)遍歷執(zhí)行每一個(gè)timer
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}
  • 下面我們?cè)倏纯?code>__CFRunLoopDoTimer方法:
// mode and rl are locked on entry and exit
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {   /* DOES CALLOUT */
    Boolean timerHandled = false;
    uint64_t oldFireTSR = 0;

    /* Fire a timer */
    CFRetain(rlt);
    __CFRunLoopTimerLock(rlt);

    if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {
        void *context_info = NULL;
        void (*context_release)(const void *) = NULL;
        if (rlt->_context.retain) {
            context_info = (void *)rlt->_context.retain(rlt->_context.info);
            context_release = rlt->_context.release;
        } else {
            context_info = rlt->_context.info;
        }
        Boolean doInvalidate = (0.0 == rlt->_interval);
    __CFRunLoopTimerSetFiring(rlt);
        // Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
        rlm->_timerSoftDeadline = UINT64_MAX;
        rlm->_timerHardDeadline = UINT64_MAX;
        __CFRunLoopTimerUnlock(rlt);
    __CFRunLoopTimerFireTSRLock();
    oldFireTSR = rlt->_fireTSR;
    __CFRunLoopTimerFireTSRUnlock();

        __CFArmNextTimerInMode(rlm, rl);

    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
        // 與上面在調(diào)用堆棧中看到的方法一致
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
    CHECK_FOR_FORK();
        if (doInvalidate) {
            CFRunLoopTimerInvalidate(rlt);      /* DOES CALLOUT */
        }
        if (context_release) {
            context_release(context_info);
        }
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
        __CFRunLoopTimerLock(rlt);
    timerHandled = true;
    __CFRunLoopTimerUnsetFiring(rlt);
    }
    if (__CFIsValid(rlt) && timerHandled) {
        /* This is just a little bit tricky: we want to support calling
         * CFRunLoopTimerSetNextFireDate() from within the callout and
         * honor that new time here if it is a later date, otherwise
         * it is completely ignored. */
        if (oldFireTSR < rlt->_fireTSR) {
            /* Next fire TSR was set, and set to a date after the previous
            * fire date, so we honor it. */
            __CFRunLoopTimerUnlock(rlt);
            // The timer was adjusted and repositioned, during the
            // callout, but if it was still the min timer, it was
            // skipped because it was firing.  Need to redo the
            // min timer calculation in case rlt should now be that
            // timer instead of whatever was chosen.
            __CFArmNextTimerInMode(rlm, rl);
        } else {
        uint64_t nextFireTSR = 0LL;
            uint64_t intervalTSR = 0LL;
            if (rlt->_interval <= 0.0) {
            } else if (TIMER_INTERVAL_LIMIT < rlt->_interval) {
            intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
            } else {
            intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
            }
            if (LLONG_MAX - intervalTSR <= oldFireTSR) {
                nextFireTSR = LLONG_MAX;
            } else {
                if (intervalTSR == 0) {
                    // 15304159: Make sure we don't accidentally loop forever here
                    CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
                    HALT;
                }
                uint64_t currentTSR = mach_absolute_time();
                nextFireTSR = oldFireTSR;
                while (nextFireTSR <= currentTSR) {
                    nextFireTSR += intervalTSR;
                }
            }
            CFRunLoopRef rlt_rl = rlt->_runLoop;
            if (rlt_rl) {
                CFRetain(rlt_rl);
        CFIndex cnt = CFSetGetCount(rlt->_rlModes);
        STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
        CFSetGetValues(rlt->_rlModes, (const void **)modes);
        // To avoid A->B, B->A lock ordering issues when coming up
        // towards the run loop from a source, the timer has to be
        // unlocked, which means we have to protect from object
        // invalidation, although that's somewhat expensive.
        for (CFIndex idx = 0; idx < cnt; idx++) {
            CFRetain(modes[idx]);
        }
        __CFRunLoopTimerUnlock(rlt);
        for (CFIndex idx = 0; idx < cnt; idx++) {
            CFStringRef name = (CFStringRef)modes[idx];
            modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
            CFRelease(name);
        }
        __CFRunLoopTimerFireTSRLock();
        rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
        for (CFIndex idx = 0; idx < cnt; idx++) {
            CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
            if (rlm) {
                        __CFRepositionTimerInMode(rlm, rlt, true);
            }
        }
        __CFRunLoopTimerFireTSRUnlock();
        for (CFIndex idx = 0; idx < cnt; idx++) {
            __CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
        }
        CFRelease(rlt_rl);
        } else {
        __CFRunLoopTimerUnlock(rlt);
        __CFRunLoopTimerFireTSRLock();
        rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
        __CFRunLoopTimerFireTSRUnlock();
            }
        }
    } else {
        __CFRunLoopTimerUnlock(rlt);
    }
    CFRelease(rlt);
    return timerHandled;
}

在上面的代碼中我們可以清楚的看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__的調(diào)用,這與我們?cè)谡{(diào)用堆棧中看到的是一致的图云。

所以關(guān)于timer的調(diào)用過程是這樣的:

  1. 為自定義的timer設(shè)置mode惯悠,并將其加入到Runloop
  2. 在Runloop的run方法執(zhí)行時(shí),會(huì)調(diào)用__CFRunLoopDoTimers方法收集完timer后就會(huì)循環(huán)遍歷執(zhí)行所有timer
  3. 循環(huán)遍歷的時(shí)候調(diào)用的是__CFRunLoopDoTimer方法
  4. timer執(zhí)行完畢后會(huì)執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)

以上就是timer執(zhí)行的簡(jiǎn)單分析竣况,對(duì)于observer克婶、blocksource0丹泉、source1等的指向也都大同小異情萤,感興趣的還可以去看看官方文檔

3. Runloop 的原理

3.1 CFRunLoopRun

分析到這里我們重新回到最初的地方CFRunLoopRun摹恨,這個(gè)可以通過堆棧信息中看出筋岛,也可以從上面的分析中總結(jié)出來。

void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

這里是一個(gè)do while循環(huán)晒哄,也就是我們一開始說的那個(gè)do while循環(huán)

這里會(huì)調(diào)用CFRunLoopRunSpecific方法睁宰,其中還有個(gè)1.0e10這就是1*10^10表示超時(shí)時(shí)間。

3.2 CFRunLoopRunSpecific

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獲取到mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    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 即將進(jìn)入 loop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 調(diào)用 __CFRunLoopRun(真正run的地方) 進(jìn)入loop
    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;
}

通過上面的代碼我們知道這里主要有如下步驟:

  1. 根據(jù)modeName找到對(duì)應(yīng)的mode
  2. run前發(fā)通知Observers即將進(jìn)入loop
  3. 調(diào)用__CFRunLoopRun進(jìn)行真正的run寝凌,也就是真正的進(jìn)入到runloop
  4. 執(zhí)行完__CFRunLoopRun后也會(huì)發(fā)生通知Observers表示Runloop即將退出

3.3 __CFRunLoopRun

再一次來到__CFRunLoopRun柒傻,下面我們看點(diǎn)不一樣的,這里代碼太多了硫兰,感興趣的可以自行下載诅愚,下載地址。這里我們通過一份偽代碼來展示一下__CFRunLoopRun的主要邏輯:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    /// 首先根據(jù)modeName找到對(duì)應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    /// 通知 Observers: RunLoop 即將進(jìn)入 loop劫映。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// 內(nèi)部函數(shù),進(jìn)入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 通知 Observers: RunLoop 即將退出刹前。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}

/// 核心函數(shù)
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    
    do {
        
        /// 通知 Observers: 即將處理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        /// 通知 Observers: 即將處理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        /// 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /// 處理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        /// 處理sources0返回為YES
        if (sourceHandledThisLoop) {
            /// 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        
        /// 判斷有無端口消息(Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 處理消息
            goto handle_msg;
        }
        
        /// 通知 Observers: 即將進(jìn)入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// 等待被喚醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        /// 通知 Observers: 被喚醒泳赋,結(jié)束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:
        if (被Timer喚醒) {
            /// 處理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        } else if (被GCD喚醒) {
            /// 處理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else if (被Source1喚醒) {
            /// 被Source1喚醒喇喉,處理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        /// 處理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            // 處理source
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            // 超時(shí)處理
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            // 被外部調(diào)用者強(qiáng)制停止了
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            // 自動(dòng)停止
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // 什么都沒有了祖今,結(jié)束了
            retVal = kCFRunLoopRunFinished;
        }
        // 如果沒超時(shí),mode里沒空,loop也沒被停止千诬,那繼續(xù)loop耍目。
    } while (0 == retVal);
    
    return retVal;
}

所以我們可以知道這里主要的邏輯就是根據(jù)不同的事件源進(jìn)行不同的處理,當(dāng)runloop休眠時(shí)徐绑,可以通過響應(yīng)的時(shí)間喚醒Runloop邪驮。

還是放一份源碼吧,有點(diǎn)多傲茄,感興趣的慢慢看:

/* rl, rlm are locked on entrance and exit */
/**
 *  運(yùn)行run loop
 *
 *  @param rl              運(yùn)行的RunLoop對(duì)象
 *  @param rlm             運(yùn)行的mode
 *  @param seconds         run loop超時(shí)時(shí)間
 *  @param stopAfterHandle true:run loop處理完事件就退出  false:一直運(yùn)行直到超時(shí)或者被手動(dòng)終止
 *  @param previousMode    上一次運(yùn)行的mode
 *
 *  @return 返回4種狀態(tài)
 */

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    //獲取系統(tǒng)啟動(dòng)后的CPU運(yùn)行時(shí)間毅访,用于控制超時(shí)時(shí)間
    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為超時(shí)時(shí)間草巡,超時(shí)時(shí)執(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
        //永不超時(shí) - 永動(dòng)機(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 {
        //初始化一個(gè)存放內(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事件處理 并且 沒有超時(shí),poll為false
        //如果有Sources0事件處理 或者 超時(shí)萍歉,poll都為true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        //第一次do..whil循環(huán)不會(huì)走該分支侣颂,因?yàn)閐idDispatchPortLastTime初始化是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),直接處理這個(gè) 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
        
        //這里有個(gè)內(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)入休眠, 直到被下面某一個(gè)事件喚醒低零。
        /// ? 一個(gè)基于 port 的Source 的事件。
        /// ? 一個(gè) Timer 到時(shí)間了
        /// ? RunLoop 自身的超時(shí)時(shí)間到了
        /// ? 被其他什么調(diào)用者手動(dòng)喚醒
        
        // 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 如果一個(gè) Timer 到時(shí)間了潭陪,觸發(fā)這個(gè)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 如果一個(gè) Source1 (基于port) 發(fā)出事件了钧忽,處理這個(gè)事件
            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í)參數(shù)說處理完事件就返回。
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            /// 被外部調(diào)用者強(qiáng)制停止了
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            /// 自動(dòng)停止了
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            /// source/timer/observer一個(gè)都沒有了
            retVal = kCFRunLoopRunFinished;
        }
        /// 如果沒超時(shí)瘟则,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;
}

綜上所述,我們通過一個(gè)流程圖來總結(jié)一下Runloop:

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宿百,一起剝皮案震驚了整個(gè)濱河市趁仙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌垦页,老刑警劉巖雀费,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痊焊,居然都是意外死亡盏袄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門薄啥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辕羽,“玉大人,你說我怎么就攤上這事垄惧〉笤福” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵到逊,是天一觀的道長(zhǎng)铣口。 經(jīng)常有香客問我,道長(zhǎng)觉壶,這世上最難降的妖魔是什么脑题? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮铜靶,結(jié)果婚禮上叔遂,老公的妹妹穿的比我還像新娘。我一直安慰自己争剿,他們只是感情好已艰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秒梅,像睡著了一般旗芬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捆蜀,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼辆它。 笑死誊薄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锰茉。 我是一名探鬼主播呢蔫,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼飒筑!你這毒婦竟也來了片吊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤协屡,失蹤者是張志新(化名)和其女友劉穎俏脊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肤晓,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爷贫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了补憾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漫萄。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖盈匾,靈堂內(nèi)的尸體忽然破棺而出腾务,到底是詐尸還是另有隱情,我是刑警寧澤削饵,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布岩瘦,位于F島的核電站,受9級(jí)特大地震影響葵孤,放射性物質(zhì)發(fā)生泄漏担钮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一尤仍、第九天 我趴在偏房一處隱蔽的房頂上張望箫津。 院中可真熱鬧,春花似錦宰啦、人聲如沸苏遥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽田炭。三九已至,卻和暖如春漓柑,著一層夾襖步出監(jiān)牢的瞬間教硫,已是汗流浹背叨吮。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞬矩,地道東北人茶鉴。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像景用,于是被迫代替她去往敵國和親涵叮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容