天天看點

從源碼看runLoop

源碼比文字更令人深刻

版本: CF-1151.16

官方文檔

相關API

源碼下載下傳

為了文章簡潔,隻摘抄部分主要的代碼 源碼詳細介紹在這裡

一、邂逅runLoop

應該是一個美麗的下午,在一場面試上,遇見了runLoop,可惜擦肩而過。。。

二、認識runLoop

CFRunLoop

struct __CFRunLoop {
    pthread_t _pthread;            		 // runLoop 對應的線程
    
    __CFPort _wakeUpPort;				// 用來喚醒runLoop的端口,接收消息,執行CFRunLoopWakeUp方法
    
    CFMutableSetRef _commonModes;       // 集合,所有标記為common的mode的集合
    
    CFMutableSetRef _commonModeItems;   // 集合,commonMode的item(observers/sources/timers)的集合
    
    CFRunLoopModeRef _currentMode;      // 目前runLoop運作的mode
    
    CFMutableSetRef _modes;             // 集合,mode的集合
};
複制代碼
           

從源碼可以看出一部分内容 : 一個runLoop對象,主要包含一個線程

_pthread

,一個用來被喚醒的端口

_wakeUpPort

,一個目前運作的mode

_currentMode

,以及若幹個

_modes

_commonModes

_commonModeItems

。 runLoop有很多mode,即

_modes

,但是隻有一個

_currentMode

,runLoop一次隻能運作在一個mode下,不可能在多個mode下同時運作。

CFRunLoopMode

struct __CFRunLoopMode {
	CFStringRef _name;      // mode的名字,唯一辨別
    
    Boolean _stopped;       // mode的狀态,是否停止
    
    CFMutableSetRef _sources0;  // sources0 的集合
    
    CFMutableSetRef _sources1;  // sources1 的集合
    
    CFMutableArrayRef _observers;   // 存儲所有觀察者(observers)的數組
    
    CFMutableArrayRef _timers;      // 存儲所有定時器(timers)的數組
    
    // 源碼中有一段代碼,可以看出字典的存儲對象
    // CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
    CFMutableDictionaryRef _portToV1SourceMap;  // 字典 key是__CFPort,value是CFRunLoopSourceRef
    
    // __CFPortSetInsert(src_port, rlm->_portSet);
    __CFPortSet _portSet;           // 端口的集合
}
複制代碼
           

從mode的組成可以看出來:mode管理了所有的事件(sources/timers/observers),而runLoop是管理mode的

CFRunLoopSource

struct __CFRunLoopSource {
	CFMutableBagRef _runLoops; 				// 一個Source 對應多個runLoop
	
	union {
        
        CFRunLoopSourceContext version0; 	// source0
        
        CFRunLoopSourceContext1 version1; 	//source1	
        
    } _context;
	
}
// source0
typedef struct {
    CFIndex	version; 	// 版本号,用來區分是source1還是source0

    void *	info;
    
    // schedule cancel 是對應的,
    void	(*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void	(*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);

    void	(*perform)(void *info); // 用來回調的指針
   
} CFRunLoopSourceContext;

// source1
typedef struct {
    CFIndex	version; 	// 版本号
    void *	info;
    
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t	(*getPort)(void *info); // 端口
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *	(*getPort)(void *info);
    
    void	(*perform)(void *info); // 用來回調的指針
#endif
} CFRunLoopSourceContext1;
複制代碼
           

源碼中看出來,source0和source1的差別,source1比source0多一個接收消息的端口

mach_port_t

CFRunLoopObserver

struct __CFRunLoopObserver {
   
    CFRunLoopRef _runLoop;         // observer對應的runLoop, 一一對應
    
    CFIndex _rlCount;              //  observer目前監測的runLoop數量,主要在安排/移除runLoop的時候用到
    
    CFOptionFlags _activities;      // observer觀測runLoop的狀态,枚舉類型,
    
    CFIndex _order;                 // mode使用數組存儲observers,根據_order添加observer
    
    CFRunLoopObserverCallBack _callout; 
};
複制代碼
           

_activities

狀态值:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),               // 即将進入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),        // runLoop即将處理 Timers
    kCFRunLoopBeforeSources = (1UL << 2),       // runLoop即将處理 Sources
    kCFRunLoopBeforeWaiting = (1UL << 5),       // runLoop即将進入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),        // runLoop剛從休眠中喚醒
    kCFRunLoopExit = (1UL << 7),                // 即将退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU       
};
複制代碼
           

CFRunLoopTimer

struct __CFRunLoopTimer {
    
    CFRunLoopRef _runLoop;          // timer 對應的runLoop
    CFMutableSetRef _rlModes;       // 集合,存放對應的modes,猜測一個timer 可以有多個modes,即可以被加入到多個modes中
    
    CFRunLoopTimerCallBack _callout;
};
複制代碼
           

三、了解runLoop

5個類之間的主要方法,來詳細了解類之間的互相關系

CFRunLoopCopyCurrentMode

擷取runLoop正在運作的mode(即

_currentMode

)的name。
CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl) {

	    CFStringRef result = NULL;

       result = (CFStringRef)CFRetain(rl->_currentMode->_name);
	    return result;
}
複制代碼
           

CFRunLoopCopyAllModes

傳回一個數組,其中包含了runLoop所有定義過的mode(即

_modes

)的name
CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl) {

	    CFMutableArrayRef array;
	    
	    array = CFArrayCreateMutable(kCFAllocatorSystemDefault, CFSetGetCount(rl->_modes), &kCFTypeArrayCallBacks);
	    
	    // CFSetApplyFunction 三個參數a,b,c,
	    // 表示:對a裡面的每個對象,都執行一次b方法,b方法的參數是a和c,後面會多次遇到
	    CFSetApplyFunction(rl->_modes, (__CFRunLoopGetModeName), array);
	    
	    return array;
}

  // 把mode的name添加進數組array
static void __CFRunLoopGetModeName(const void *value, void *context) {
	    CFRunLoopModeRef rlm = (CFRunLoopModeRef)value;
	    CFMutableArrayRef array = (CFMutableArrayRef)context;
	    CFArrayAppendValue(array, rlm->_name);
}
複制代碼
           

CFRunLoopAddCommonMode

向runLoop的commonModes添加一個mode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
	    
	    // 判斷 modeName 是否在_commonModes 中,如果已經存在,else中不做任何處理
	    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
	        
	        // set 是 runLoop 的 _commonModeItems一份拷貝
	        CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
	        // 1. _commonModes 添加 modeName,
	        // 可見_commonModes存儲的其實是CFStringRef類型的modeName
	        CFSetAddValue(rl->_commonModes, modeName);
	        
	        // 如果items 存在
	        if (NULL != set) {
	            CFTypeRef context[2] = {rl, modeName};
	            // 2. 為modeName對應的Mode添加items中的每個item(timer/source/observer)
	            // 為set中的每個item,調用一次__CFRunLoopAddItemsToCommonMode方法
	            CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
	        }
	    } else {
	    }
}

 // 把一個item添加到指定的mode中
static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx) {
    
	    CFTypeRef item = (CFTypeRef)value;
	    
	    CFRunLoopRef rl = ()(((CFTypeRef *)ctx)[0]);
	    
	    CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]);
	    
	    // 判斷item具體是哪種類型,然後進行添加
	    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
	        CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
	    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
	        CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
	    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
	        CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
	    }
}
複制代碼
           

CFRunLoopAddSource

添加一個source到指定的runLoopMode
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {	/* DOES CALLOUT */
	    
	    // 聲明一個bool值的辨別,後續用來source0 添加source
	    Boolean doVer0Callout = false;
	    
	    // 1. 如果是commonMode,那麼commonModes中的所有mode都要更新
	    if (modeName == kCFRunLoopCommonModes) {
		    /*
		    這裡擷取rl->_commonModes并指派set,如果沒有為NULL
		    同時擷取rl->_commonModeItems,如果不存在就初始化建立
		    */
	        // 1.1 先把 rls 添加進_commonModeItems
	        CFSetAddValue(rl->_commonModeItems, rls);
            // 1.2 為set中其他的mode,添加rls 
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);  

	    }
	    // 2. 非commonMode的添加 
	    else {
	        // 2.1 在runLoop的_modes中查找名字為modeName的mode,找不到會在内部進行初始化建立(true決定是否建立)
	        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	        
	        // 2.2 擷取mode的跟source有關的_sources0,_sources1以及端口_portToV1SourceMap
	        if (NULL != rlm && NULL == rlm->_sources0) {
	            rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	            rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	            rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
	        }
	        
	        
            // 2.3 判斷rls屬于哪種類型,并針對性的添加 
            // 2.3.1 source0的情況
            if (0 == rls->_context.version0.version) {
                CFSetAddValue(rlm->_sources0, rls);
                // 下面這段代碼是後面的,放在這裡便于了解,source0 有個schedule指針,把rl和rlm關聯起來
                rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName);
            }
            // 2.3.2 source1的情況 
            else if (1 == rls->_context.version0.version) {
                CFSetAddValue(rlm->_sources1, rls);
                // 擷取rls的端口
                __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
                // rls和端口一一對應,并存儲在mode的字典_portToV1SourceMap中
                CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
               // 把source1 的端口添加進mode的端口集合_portSet中
                __CFPortSetInsert(src_port, rlm->_portSet);
            }
            // 2.4 把rl 加入到rls的_runLoops中,即一個resources可以對應多個runLoop
            if (NULL == rls->_runLoops) {
                
                rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
            }
            CFBagAddValue(rls->_runLoops, rl);
       }
}
複制代碼
           

CFRunLoopAddObserver

添加rlo到指定的rlm
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
複制代碼
           

内部實作

CFRunLoopSource

跟差不多,都是根據mode是否commonMode分兩種情況,差别在于:

  • 關聯mode:mode有一個數組

    _observers

    ,添加是根據rlo的

    _order

    進行添加的
  • 關聯rl:根據

    _rlCount

    是否為0。隻有當rlo的

    _rlCount

    為0時,其

    _runLoop

    才是rl。

CFRunLoopAddTimer

添加rlt到指定的rlm
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
複制代碼
           

内部實作同上,差別:

  1. rlt隻能添加到其

    _runLoop

    的mode中,如果rl不是其

    _runLoop

    ,直接傳回
if (NULL == rlt->_runLoop) {
           rlt->_runLoop = rl;
       } else if (rl != rlt->_runLoop) {
           __CFRunLoopTimerUnlock(rlt);
           __CFRunLoopModeUnlock(rlm);
           __CFRunLoopUnlock(rl);
           return;
       }
複制代碼
           
  1. rlt有一個變量

    _rlModes

    ,其存儲的是rlt所在的mode的name
CFSetAddValue(rlt->_rlModes, rlm->_name);
複制代碼
           
  1. rlm有一個變量

    _timers

    ,其存儲timer是根據timer的啟動時間,即

    _fireTSR

    ,進行排序的

四、擷取runLoop

runLoop跟其所線上程是一一對應的
  1. API提供了兩個擷取runLoop的方法
CFRunLoopRef CFRunLoopGetMain(void) {
	    static CFRunLoopRef __main = NULL; // no retain needed
	    
	    // pthread_main_thread_np() 主線程
	    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
	    return __main;
}

 CFRunLoopRef CFRunLoopGetCurrent(void) {
	   
	    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
	    if (rl) return rl;
	    // pthread_self() 目前線程
	    return _CFRunLoopGet0(pthread_self());
	}
複制代碼
           

其中,

TSD

是thread special data,表示線程私有資料,在 C++ 中,全局變量可以被所有線程通路,局部變量隻有函數内部可以通路。而 TSD 的作用就是能夠在同一個線程的不同函數中被通路。(找到的資料)

__CFTSDKeyRunLoop

是一個枚舉類型的關鍵字。

pthread_self()

可以得知,如果要擷取非主線程的runLoop,必須在該線程内部調用

CFRunLoopGetCurrent

才能擷取。

  1. 根據線程t擷取對應的runLoop
// 一個内部全局的字典
static CFMutableDictionaryRef __CFRunLoops = NULL;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    
	    // 1. 保證t不為空	   
	    if (pthread_equal(t, kNilPthreadT)) {
	        
	        t = pthread_main_thread_np();
	    }
	    
	    // 2. 建立全局字典,并存儲主線程的runLoop
	    if (!__CFRunLoops) {
	      
	        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
	    
	        // 通過pthread_main_thread_np()建立CFRunLoopRef類型的mainLoop,内部對其所有變量進行初始化,并且指派_pthread為pthread_main_thread_np()
	        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	        
	        // key是主線程的指針, value 是剛建立的mainLoop
	        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	        
	        // 比較并交換指針,
	        // 這裡比較第一個參數NULL和第三個參數 (void * volatile *)&__CFRunLoops全局字典,如果相等,系統會自動把第二參數的值賦給第三個參數,
	        // volatile的作用是 每次取得數值的方式是直接從記憶體中讀取
	        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	            CFRelease(dict);
	        }
	        
	        // coreFoundation 要手動管理記憶體, create 對應 release
	        CFRelease(mainLoop);
	    }
	    
	    // 3. 全局字典已經存在,從中擷取對應線程t的runLoop
	    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	    
	    // 如果擷取不到loop,
	    if (!loop) {
	        
	        // 根據 t 建立 一個newLoop
	        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
	       
	        // 再一次進行擷取
	        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	        
	        // 如果還不存在,就直接指派,
	        if (!loop) {
	            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
	            loop = newLoop;
	        }
	    }
	    // 4. 注冊TSD
	    if (pthread_equal(t, pthread_self())) {
	        
	        // 注冊回調,當線程銷毀時,順便也銷毀其對應的 RunLoop
	        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
	        
	        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
	            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
	        }
	    }
	    return loop;
}
複制代碼
           

線程和runLoop是一一對應,儲存在一個全局字典裡,主線程的runLoop是在初始化字典時已經建立好了,其他線程的runLoop隻有在擷取的時候才會建立。

五、運作runLoop

CFRunLoopRun

預設情況下,運作目前線程的runLoop
void CFRunLoopRun(void) {	
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
複制代碼
           

源碼得知:

  1. kCFRunLoopDefaultMode

    ,預設情況下,runLoop是在這個mode下運作的,
  2. runLoop的運作主體是一個do..while循環,除非停止或者結束,否則runLoop會一直運作下去

CFRunLoopRunInMode

在指定的mode下運作目前線程的runLoop
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
複制代碼
           

該方法,可以設定runLoop運作在哪個mode下

modeName

,逾時時間

seconds

,以及是否處理完事件就傳回

returnAfterSourceHandled

。 這兩個方法實際調用的是同一個方法

CFRunLoopRunSpecific

,其傳回是一個

SInt32

類型的值,根據傳回值,來決定runLoop的運作狀況。

CFRunLoopRunSpecific

在指定的mode下,運作指定的runLoop
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {    
    // 根據rl,modeName擷取指定的currentMode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    // 1. 如果目前mode 不存在,或者目前mode中事件為空,runLoop 結束,傳回 kCFRunLoopRunFinished
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        // 聲明一個辨別did,預設false
        Boolean did = false;
        // did 為 false,傳回 kCFRunLoopRunFinished
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    
    // 初始化一個傳回結果,值為kCFRunLoopRunFinished
    int32_t result = kCFRunLoopRunFinished;

	// 2. kCFRunLoopEntry, 通知observers 即将開始循環
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // runLoop運作主體
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // 3. kCFRunLoopExit, 通知 observers 即将退出循環runLoop
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}
複制代碼
           

這裡有3點:

  1. kCFRunLoopRunFinished mode中沒有事件處理,直接傳回
  2. kCFRunLoopEntry runLoop即将開始運作,通知observers
  3. kCFRunLoopExit runLoop 即将退出,通知observers

__CFRunLoopRun

這裡處理了runLoop從開始運作到退出的所有邏輯
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    // 1. 如果runLoop停止或者runLoopMode為停止狀态,直接傳回 kCFRunLoopRunStopped
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	rlm->_stopped = false;
	   return kCFRunLoopRunStopped;
    }
    
    // 擷取主線程用來接收消息的端口
    dispatchPort = _dispatch_get_main_queue_port_4CF();
   
    // 擷取執行timers對應的線程的端口
    modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    
    // GCD 管理的定時器,用于實作runLoop的逾時機制
    dispatch_source_t timeout_timer = NULL;    
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    
    // 處理timer 三種情況 :timer1 立即逾時
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
        
        // timer2 即将逾時
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
			// 判斷在哪個線程中執行
        dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
        
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

        // 事件一一對應,
        dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        // 定時器執行
        dispatch_resume(timeout_timer);
        
    } else {
        // timer3 永不逾時
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }

    // 聲明一個辨別,預設true,用于執行消息處理
    Boolean didDispatchPortLastTime = true;
    // 聲明一個傳回值,用于最後的結果傳回
    int32_t retVal = 0;
    
    // do..while循環主體,處理runLoop的邏輯
    do {

        // 擷取rlm的端口集合
        __CFPortSet waitSet = rlm->_portSet;
       // runLoop設定為可被喚醒的狀态
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        
        // 2. kCFRunLoopBeforeTimers runLoop即将處理Timers, 通知observers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 3. kCFRunLoopBeforeSources runLoop即将處理Sources,通知observers
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // 4. runLoop開始處理source0事件
        // sourceHandledThisLoop 是否處理完Source0事件
        // 内部實作是,隻有被标記Signaled的source0事件才會被處理,但在處理之前會去除标記__CFRunLoopSourceUnsetSignaled
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        if (sourceHandledThisLoop) {
            // 處理完Source0之後的回調
            __CFRunLoopDoBlocks(rl, rlm);
        }

        // 處理完source0事件,且沒有逾時 poll 為false, 
        // 沒有處理完source0 事件,或者逾時,為true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        // didDispatchPortLastTime 初始化為true,即第一次循環的時候不會走if方法,
        // 5. 消息處理,source1 事件,goto 第9步
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {

            // 從消息緩沖區擷取消息
            msg = (mach_msg_header_t *)msg_buffer;
            // dispatchPort收到消息,立刻去處理 
            // dispatchPort 主線程接收消息的端口
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                // 收到消息,立馬去處理
                goto handle_msg;
            }

            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }

        }
        // didDispatchPortLastTime 設定為false,以便進行消息處理
        didDispatchPortLastTime = false;

        // 6. kCFRunLoopBeforeWaiting,通知 observers runLoop即将休眠
		if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        // runLoop 休眠
        __CFRunLoopSetSleeping(rl);
        
        // 7.線程進入休眠, 直到被下面某一個事件喚醒。(文檔給出的結果:)
	    // 7.1. 基于 port 的Source1 的事件
	    // 7.2. Timer 到時間了
	    // 7.3. RunLoop 啟動時設定的最大逾時時間到了
	    // 7.4. 被手動喚醒
        do {
            // 從消息緩沖區擷取消息
            msg = (mach_msg_header_t *)msg_buffer;
				// 内部調用 mach_msg() 等待接受 waitSet 的消息
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);

        // 設定rl不再等待喚醒
        __CFRunLoopSetIgnoreWakeUps(rl);
        // runloop 醒來
        __CFRunLoopUnsetSleeping(rl);
        
        // 8. kCFRunLoopAfterWaiting 已被喚醒,通知observers
	   if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        // 9. 處理消息
        handle_msg:;
        // 設定rl不再等待喚醒
        __CFRunLoopSetIgnoreWakeUps(rl);

        // 判斷 livePort
        // 9.1 如果不存在
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // 9.2 如果是喚醒rl的端口,回到第2步
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            ResetEvent(rl->_wakeUpPort);
        }
        // 定時器事件__CFRunLoopDoTimers
        // 9.3 如果是定時器的端口
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            // 處理定時器事件
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        // 9.4. 如果端口是主線程的端口,直接處理
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);     
        } else {
            // 9.5. 除上述4點之外的端口
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            
            // 從端口收到的消息事件,為source1事件
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            
            if (rls) {

                mach_msg_header_t *reply = NULL;
                        // 處理source1 事件
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                		// 消息處理,
                		// message.h中,以後有時間會再研究一下
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                }

            }
            
        } 
        // 10. 傳回結果的處理  
        if (sourceHandledThisLoop && stopAfterHandle) {
            // 10.1 如果事件處理完就傳回,并且source處理完成
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            // 10.2 逾時
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            // 10.3 被外部調用者強制停止了
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            // 10.4 runLoopMode 狀态停止
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // 10.5 source/timer/observer一個都沒有了
            retVal = kCFRunLoopRunFinished;
        }
        // 上述幾種情況,會跳出do..while循環,
        // 除此之外,繼續循環
    } while (0 == retVal);
    return retVal;
}
複制代碼
           

上述2-10就是runLoop運作過程中的循環邏輯,而最終傳回的狀态有:

kCFRunLoopRunFinished

kCFRunLoopRunStopped

kCFRunLoopRunTimedOut

以及

kCFRunLoopRunHandledSource

四種枚舉類型

總結:

1. runLoop跟線程一一對應,非主線程的rl隻能在其内部擷取,runLoop管理rlm和回調block,而rlm存儲了所有的事件。

2. runLoop運作核心就是一個do..while循環,周遊所有事件,有事件處理,無事件休眠,直至達到退出條件。

3. 以上就是runLoop内部的源碼分析,當然會有了解不到位的情況,也留有待解決的問題,萬望不吝賜教。

參考資料:

深入了解RunLoop

RunLoop系列之源碼分析

Run Loops

CFRunLoop