iOS RunLoop 詳解

轉(zhuǎn)自 iOS RunLoop 詳解

image.png

Runloop 是和線程緊密相關(guān)的基礎(chǔ)組件,是很多多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不會太直接用到,理解 Runloop 有利于我們更加深入的理解 iOS 的多線程模型。

本文從如下幾個方面理解RunLoop的相關(guān)知識點(diǎn)。

  • RunLoop 概念
  • RunLoop 實(shí)現(xiàn)
  • RunLoop 運(yùn)用
  • RunLoop 應(yīng)用

RunLoop 概念

RunLoop 介紹

RunLoop 是什么?RunLoop 還是比較顧名思義的一個東西,說白了就是一種循環(huán),只不過它這種循環(huán)比較高級。一般的 while 循環(huán)會導(dǎo)致 CPU 進(jìn)入忙狀態(tài),而 RunLoop 則是一種“閑”等待,這部分可以類比 Linux 下的 epoll。當(dāng)沒有事件時,RunLoop 會進(jìn)入休眠狀態(tài),有事件發(fā)生時,RunLoop 會去找相應(yīng)的 Hander 處理事件。RunLoop 可以讓線程需要做事的時候忙起來,不需要的話就讓線程休眠。

從代碼上看,RunLoop 其實(shí)就是一個對象,它的結(jié)構(gòu)如下,源碼看這里

struct __CFRunLoop {
  CFRuntimeBase _base;
  pthread_mutex_t _lock; /* locked for accessing mode list */
  __CFPort _wakeUpPort; // used for CFRunLoopWakeUp 內(nèi)核向該端口發(fā)送消息可以喚醒 RunLoop
  Boolean _unused;
  volatile _per_run_data *_perRunData; // reset for runs of the run loop
  pthread_t _pthread; // RunLoop 對應(yīng)的線程
  uint32_t _winthread;
  CFMutableSetRef _commonModes; // 存儲的是字符串,記錄所有標(biāo)記為 common 的 mode
  CGMutableSetRef _commonModeItems; // 存儲所有 commonMode 的 item(source、timer、observer)
  CFRunLoopModeRef _currentMode; // 當(dāng)前運(yùn)行的 mode
  CFMutableSetRef _modes; // 存儲的是 CFRunLoopModeRef
  struct _block_item *_blocks_head; // doblocks 的時候用到
  struct _block_item *_blocks_tail;
  CGTypeRef _counterpart;
}

可見,一個 RunLoop 對象,主要包含了一個線程,若干個 Mode,若干個 commonMode,還有一個當(dāng)前運(yùn)行的 Mode。

RunLoop 與線程

當(dāng)我們需要一個常駐線程,可以讓線程需要做事的時候忙起來,不需要的話就讓線程休眠。我們就在線程里面執(zhí)行下面這個代碼,一直等待消息,線程就不會退出了。

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

上面的這種循環(huán)模型被稱作 Event Loop,事件循環(huán)模型在眾多系統(tǒng)里都有實(shí)現(xiàn),RunLoop 實(shí)際上就是一個對象,這個對象管理了其需要處理的事件和消息,并提供了一個入口函數(shù)來執(zhí)行上面 Event Loop 的邏輯。線程執(zhí)行了這個函數(shù)后,就會一直處于函數(shù)內(nèi)部“接收消息->等待->處理”的循環(huán)中,直到這個循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。

下圖描述了Runloop運(yùn)行流程(基本描述了上面Runloop的核心流程,當(dāng)然可以查看官方The Run Loop Sequence of Events描述):

runLoopFCD.jpg

整個流程并不復(fù)雜(需要注意的就是黃色區(qū)域的消息處理中并不包含source0,因?yàn)樗谘h(huán)開始之初就會處理),整個流程其實(shí)就是一種Event Loop的實(shí)現(xiàn),其他平臺均有類似的實(shí)現(xiàn),只是這里叫做RunLoop。

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

image.png

圖中展現(xiàn)了 RunLoop 在線程中的作用:從 input source 和 timer source 接受事件,然后在線程中處理事件。

RunLoop 和線程是綁定在一起的。每個線程(包括主線程)都有一個對應(yīng)的 RunLoop 對象。我們并不能自己創(chuàng)建 RunLoop 對象,但是可以獲取到系統(tǒng)提供的 RunLoop 對象。

主線程的 RunLoop 會在應(yīng)用啟動的時候完成啟動,其他線程的 RunLoop 默認(rèn)并不會啟動,需要我們手動啟動。

RunLoop Mode

Mode 可以視為事件的管家,一個 Mode 管理著各種事件,它的結(jié)構(gòu)如下:

struct __CFRunLoopMode {
  CFRuntimeBase _base;
  pthread_mutex_t _lock; /* must have the run loop locked before locking this */
  CFStringRef _name; // mode 名稱
  Boolean _stopped; // mode 是否被終止
  char _padding[3];
  // 幾種事件
  CFMutableSetRef _sources0; // source0
  CFMutableSetRef _sources1: // sources1
  CFMutableArrayRef _observers; // 通知
  CFMutableArrayRef _times; // 定時器
  CFMutableDictionaryRef _portToV1SourceMap; // 字典 key 是 mach_port_t,value 是 CFRunLoopSourceRef
  __CFPortSet _portSet; // 保存所有需要監(jiān)聽的 port,比如 _weakUpPort, _timerPort 都保存在這個數(shù)據(jù)中
  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 DEPLOYMENAT_TARGET_WINDOWS
  DWORD _msgQMask;
  void (* _msgPump)(void);
#endif
  uint64_t _timerSoftDeadline;
  uint64_t _timerHardDeadline;
}

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

從源碼很容易看出,RunLoop 總是運(yùn)行在某種特定的 CFRunLoopModeRef 下(每次運(yùn)行 __CFRunLoopRun() 函數(shù)時必須指定 Mode)。而通過 CFRunLoopRef 對應(yīng)結(jié)構(gòu)體的定義可以很容易知道每種 RunLoop 都可以包含個 Mode,每個 Mode 有包含 Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù) __CFRunLoopRun() 時必須指定一種 Mode,這個 Mode 稱為 _curentMode,當(dāng)切換 Mode 時必須退出當(dāng)前 Mode,然后重新進(jìn)入 RunLoop 以保證不同 Mode 的 Source/Timer/Observer 互不影響。

image.png

如圖所示,RunLoop Mode 實(shí)際上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同組的 Source,Timer 和 Observer 隔絕開來。RunLoop 在某個時刻只能跑在一個 Mode 下,處理這一個 Mode 當(dāng)中的 Source,Timer 和 Observer。

蘋果文檔中提到的 Mode 有五個,分別是:

  • NSDefaultRunLoopMode
  • NSConnectionReplyMode
  • NSModalPanelRunLoopMode
  • NSEventTrackingRunLoopMode
  • NSRunLoopCommonModes

iOS 中公開暴露出來的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。NSRunLoopCommonModes 實(shí)際上是一個 Mode 的集合,默認(rèn)包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode(注意:并不是說 RunLoop 會運(yùn)行在 kCFRunLoopCommonModes 這種模式下,而是相當(dāng)于分別注冊了 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。當(dāng)然你也可以通過調(diào)用 CFRunLoopAddCommonMode() 方法將自定義 Mode 放到 kCFRunLoopCommonModes 組合)。

五種 Mode 的介紹如下:

mode name description
Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) 用的最多的模式,大多數(shù)情況下應(yīng)該使用該模式開始 RunLoop 并配置 input source
Connection NSConnectionReplyMode (Cocoa) Cocoa 用這個模式結(jié)合 NSConnection 對象監(jiān)測回應(yīng),我們應(yīng)該很少使用這種模式
Modal NSModalPanelRunLoopMode (Cocoa) Cocoa 用此模式來表示用于模態(tài)面板的事件
Event tracking NSEventTrackingRunLoopMode (Cocoa) Cocoa 使用此模式在鼠標(biāo)拖動 loop 和其它用戶界面跟蹤 loop 期間限制傳入事件
Common modes NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation) 這是一種可配置的常用模式。將輸入源與這些模式相關(guān)聯(lián)會與組中的每個模式相關(guān)聯(lián)。Cocoa applications 里面包括 Default、Modal 和 Event tracking。Core Foundation 只包含默認(rèn)模式,你可以自己把自定義 mode 用 CFRunLoopAddCommonMode 函數(shù)加入到集合中。

RunLoop Source

RunLoop source 分為 Source、Observer、Timer 三種,它們統(tǒng)稱為 ModelItem。

CFRunLoopSource

根據(jù)官方的描述,CFRunLoopSource 是對 input sources 的抽象。CFRunLoopSource 分為 source0 和 source1 兩個版本,它的結(jié)構(gòu)如下:

struct __CFRunLoopSource {
  CFRuntimeBase _base;
  uint32_t _bits; // 用于標(biāo)記 Signaled 狀態(tài),source0 只有在被標(biāo)記為 Signaled 狀態(tài),才會被處理
  pthread_mutex_t _lock;
  CFIndex _order; /* immutable */
  CFMutableBagRef _runLoops;
  union {
    CFRunLoopSourceContext version0; /* immutable, except invalidation */
    CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
  } _context;
};

source0 是 App 內(nèi)部事件,由 App 自己管理的 UIEvent、CFSocket 都是 source0.當(dāng)一個 source0 事件準(zhǔn)備執(zhí)行的時候,必須要先把它標(biāo)記為 signal 狀態(tài),以下是 source0 的結(jié)構(gòu)體:

typedef struct {
  CFIndex version;
  void *info;
  const void *(*retain)(const void *info);
  void (*release)(const void *info);
  CFStringRef (*copyDescription)(const void *info);
  Boolean (*equal)(const void *info1, const void *info2);
  CFHashCode (*hash)(const void *info);
  void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
  void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
  void (*perform)(void *info);
} CFRunLoopSourceContext;

source0 是非基于 Port 的。只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件。石永師,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。

source1 由 RunLoop 和內(nèi)核管理,source1 帶有 mach_port_t,可以接收內(nèi)核消息并觸發(fā)回調(diào),以下是 source1 的結(jié)構(gòu)體:

typedef struct {
  CFIndex version
  void *info;
  const void *(*retain)(const void *info);
  void (*release)(const void *info);
  CFStringRef (*copyDescription)(const void *info);
  Boolean (*equal)(const void *info1, const void *info2);
  CFHashCode (*hash)(const 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;

Source1 除了包含回調(diào)指針外包含一個 mach port,Source1 可以監(jiān)聽系統(tǒng)端口和通過內(nèi)核和其他線程通信,接收、分發(fā)系統(tǒng)事件,它能夠主動喚醒 RunLoop(由操作系統(tǒng)內(nèi)核進(jìn)行管理,例如 CFMessagePort 消息)。官方也指出可以自定義 Source,因此對于 CFRunLoopSourceRef 來說它更像一種協(xié)議,框架已經(jīng)默認(rèn)定義了兩種實(shí)現(xiàn),如果有必要開發(fā)人員也可以自定義,詳細(xì)情況可以查看官方文檔。

CFRunLoopObserver

CFRunLoopObserver 是觀察者,可以觀察 RunLoop 的各種狀態(tài),并拋出回調(diào)

struct __CFRunLoopObserver {
  CFRuntimeBase _base;
  pthread_mutex_t _lock;
  CFRunLoopARef _runLoop;
  CFIndex _rlCount;
  CFOptionFlags _activities; /* immutable */
  CFIndex _order; /* immutable */
  CFRunLoopObserverCallBack _callout; /* immutable */
  CFRunLoopObserverContext _context; /* immutable, except invalidation */
};

CFRunLoopObserver 可以觀察的狀態(tài)有如下6種:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
  kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入 run loop
  kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 timer
  kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 source
  kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
  kCFRunLoopAfterWaiting = (1UL << 6), // 被喚醒但是還沒開始處理事件
  kCFRunLoopExit = (1UL << 7), // run loop 已經(jīng)退出
  kCFRunLoopAllActivities = 0x0FFFFFFFU
};

RunLoop 通過監(jiān)控 Source 來決定有沒有任務(wù)要做,除此之外,我們還可以用 RunLoop Observer 來監(jiān)控 RunLoop 本身的狀態(tài)。RunLoop Observer 可以監(jiān)控上面的 RunLoop 事件,具體流程如下圖。

runLoopFCD2.jpg

CFRunLoopTimer

CFRunLoopTimer 是定時器,可以在設(shè)定的時間點(diǎn)拋出回調(diào),它的結(jié)構(gòu)如下:

struct __CFRunLoopTimer {
  CFRuntimeBase _base;
  uint16_t _bits; // 標(biāo)記fire狀態(tài)
  pthread_mutex_t _block;
  CFRunLoopRef _runLoop; // 添加該 timer 的 runloop
  CFMutableSetRef _rlModes; // 存放所有包含該 timer 的 modeName,意味著一個 timer 可能會在多個 mode 中存在
  CFAbsoluteTime _nextFireDate;
  CFTimeInterval _interval; // 理想時間間隔 /* immutable */
  CFTimeInterval _tolerance; // 時間偏差 /* mutable */
  uint64_t _fireTSR; /* TRS units */
  CFIndex _order; /* immutable */
  CFRunLoopTimerCallBack _callout; /* immutable */
  CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

另外根據(jù)官方文檔的描述,CFRunLoopTimer和NSTimer是toll-free bridged的,可以相互轉(zhuǎn)換。

CFRunLoopTimer is “toll-free bridged” with its Cocoa Foundation counterpart, NSTimer. This means that the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object.

所有 CFRunLoopTimer 具有以下特征:

  • CFRunLoopTimer 是定時器,可以在設(shè)定的時間點(diǎn)拋出回調(diào)
  • CFRunLoopTimer 和 NSTimer 是 toll-free bridged 的,可以互相轉(zhuǎn)換

RunLoop 實(shí)現(xiàn)

下面從以下3個方面介紹RunLoop的實(shí)現(xiàn)。

  • 獲取 RunLoop
  • 添加 Mode
  • 添加 RunLoop Source

獲取 RunLoop

從蘋果開放的 API 來看,不允許我們直接創(chuàng)建 RunLoop 對象,只能通過以下幾個函數(shù)來獲取 RunLoop:

  • CFRunLoopRef CFRunLoopGetCurrent(void)
  • CFRunLoopRef CFRunLoopGetMain(void)
  • +(NSRunLoop *)currentRunLoop
  • +(NSRunLoop *)mainRunLoop

前兩者是 Core Foundation 中的 API,后兩者是 Foundation 中的 API。

那么RunLoop是什么時候被創(chuàng)建的呢?

我們從下面幾個函數(shù)內(nèi)部看看。

CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) {
  CHECK_FOR_FORK();
  CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
  if (rl) return rl;
  // 傳入當(dāng)前線程
  return _CFRunLoopGet0(pthread_self());
}

在 CFRunLoopGetCurrent 函數(shù)中調(diào)用了 _CFRunLoopGet0(),傳入的參數(shù)是當(dāng)前線程 pthread_self()。這里可以看出,CFRunLoopGetCurrent 函數(shù)必須要在線程內(nèi)部調(diào)用,獲取當(dāng)前線程的 RunLoop。也就是說子線程的 RunLoop 必須要在子線程內(nèi)部獲取。

CFRunLoopGetMain
// 取主線程的 RunLoop
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;
}

在 CFRunLoopGetMain 函數(shù)內(nèi)部也調(diào)用了 _CFRunLoopGet0(),傳入的參數(shù)是主線程 pthread_main_thread_np()??梢钥闯觯珻FRunLoopGetMain() 不管在主線程還是子線程中調(diào)用,都可以獲取到主線程的 RunLoop。

CFRunLoopGet0

前面兩個函數(shù)都使用了 CFRunLoopGet0 實(shí)現(xiàn)傳入線程的函數(shù),下面看下 CFRunLoopGet0 的結(jié)構(gòu)是咋樣的。

static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// t==0 is a synonym for "main thread" that always works
// 根據(jù)線程取 RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
  if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
  }
  __CFSpinLock(&loopsLock);
  // 如果存儲 RunLoop 的字典不存在
  if (!__CFRunLoops) {
    __CFSpinUnlock(&loopsLock);
    // 創(chuàng)建一個臨時字典 dict
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    // 創(chuàng)建主線程的 RunLoop
    CFRunLoopRef mainLoop = _CFRunLoopCreate(pthread_main_thread_np());
    // 把主線程的 RunLoop 保存到 dict 中,key 是線程,value 是 RunLoop
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    // 此處 NULL 和 __CFRunLoops 指針都指向 NULL,匹配,所有將 dict 寫到 __CFRunLoops
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void *volatile *)&_CFRunLoops)) {
      // 釋放 dict
      CFRelease(dict);
    }
    // 釋放 mainrunloop
    CFRelease(mainLoop);
    __CFSpinLock(&loopsLock);
  }
  //以上說明,第一次進(jìn)來的時候,不管是getMainRunloop還是get子線程的runloop,主線程的runloop總是會被創(chuàng)建
  //從字典__CFRunLoops中獲取傳入線程t的runloop
  CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  __CFSpinUnlock(&loopsLock);
  // 如果沒有獲取到
  if (!loop) {
    // 根據(jù)線程 t 創(chuàng)建一個 runloop
    CFRunLoopRef newLoop = __CFRunLoopCreat(t);
    __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
      // 把 newLoop 存入字典 __CFRunLoops,key 是線程 t
      CFDictionarySetValue(__CFRunLoops, pthreadPoint(t), newLoop);
      loop = newLoop;
    }
    // don't release run loops inside the loopsLock, because CFRunLoopDellocate may end up taking it
    __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
  }
  // 如果傳入線程就是當(dāng)前線程
  if (pthread_equal(t, pthread_self())) {
    _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
    if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
      // 注冊一個回調(diào),當(dāng)線程銷毀時,銷毀對應(yīng)的 RunLoop
      _CFSetTSD(__CFTSDkeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
    }
  }
  return loop;
}

這段代碼可以總結(jié)得出以下結(jié)論:

  • RunLoop 和線程是一一對應(yīng)的,對應(yīng)的方式是以 key-value 的方式保存在一個全局字典中
  • 主線程的 RunLoop 會在初始化全局字典時創(chuàng)建
  • 子線程的 RunLoop 會在第一次獲取的時候創(chuàng)建,如果不獲取的話就一直不會被創(chuàng)建
  • RunLoop 會在線程銷毀時銷毀

添加 Mode

在 Core Foundation 中,針對 Mode 的操作,蘋果只開放了以下三個 API(Cocoa 中也有功能一樣的函數(shù),不再列出):

  • CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)
  • CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
  • CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl)

CFRunLoopAddCommonMode Adds a mode to the set of run loop common modes. 向當(dāng)前RunLoop的common modes中添加一個mode。

CFRunLoopCopyCurrentMode Returns the name of the mode in which a given run loop is currently running. 返回當(dāng)前運(yùn)行的mode的name

CFRunLoopCopyAllModes Returns an array that contains all the defined modes for a CFRunLoop object. 返回當(dāng)前RunLoop的所有mode

我們沒有辦法直接創(chuàng)建一個 CFRunLoopMode 對象,但是我們可以調(diào)用 CFRunLoopAddCommonMode 傳入一個字符串向 RunLoop 中添加 Mode,傳入的字符串即為 Mode 的名字,Mode 對象應(yīng)該是此時在 RunLoop 內(nèi)部創(chuàng)建的。下面來看一下源碼。

CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
  CHECK_FOR_FORK();
  if (__CFRunLoopIsDeallocating(rl)) return;
  __CFRunLoopLock(rl);
  // 看 rl 中是否已經(jīng)有這個 mode,如果有就什么也不做
  if (!CFSetContainsValue(rl->_commonModes, modeName)) {
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    // 把 modeName 添加到 RunLoop 的 _commonModes 中
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL != set) {
      CFTypeRef context[2] = {rl, modeName};
      /* add all common-modes items to new mode */
      // 這里調(diào)用 CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer 的時候會調(diào)用
      // __CFRunLoopFindMode(rl, modeName, true), CFRunLoopMode 對象在這個時候被創(chuàng)建
      CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
      CFRelease(set);
    }
  } else {
  }
  __CFRunLoopUnlock(rl);
}

可以看得出:

  • modeName 不能重復(fù),modeName 是 mode 的唯一標(biāo)識符
  • RunLoop 的 _commonModes 數(shù)組存放所有被標(biāo)記為 common 的 mode 的名稱
  • 添加 commonMode 會把commonModelItems 數(shù)組中的所有 source 同步到新添加的 mode 中
  • CFRunLoopMode 對象在 CFRunLoopAddItemsToCommonMode 函數(shù)中調(diào)用 CFRunLoopFindMode 時被創(chuàng)建
CFRunLoopCopyCurrentMode/CFRunLoopCopyAllModes

CFRunLoopCopyCurrentMode 和 CFRunLoopCopyAllModes 的內(nèi)部邏輯比較簡單,直接取 RunLoop 的 _currentMode 和 _modes 返回,就不貼源碼了。

添加 RunLoop Source (ModeItem)

我們可以通過以下接口添加/移除各種事件:

  • void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
  • void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
  • void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
  • void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
  • void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
  • void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
CFRunLoopAddSource

CFRunLoopAddSource 的代碼結(jié)構(gòu)如下:

//添加source事件
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
  CHECK_FOR_FORK();
  if (__CFRunLoopIsDeallocating(rl)) return;
  if (!__CFIsValid(rls)) return;
  Boolean doVer0Callout = false;
  __CFRunLoopLock(rl);
  // 如果是 kCFRunLoopCommonModes
  if (modeName == kCFRunLoopCommonModes) {
    // 如果 runloop 的 _commonModes 存在,則 copy 一個新的復(fù)制給 set
    CFSetRef set = rl->commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    // 如果 runloop 的 _commonModeItems 為空
    if (NULL == rl->_commonModeItems) {
      // 先初始化
      rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
    // 把傳入的 CFRunLoopSourceRef 加入 _commonModeItems
    CFSetAddValue(rl->_commonModeItems, rls);
    // 如果剛才 set copy 到的數(shù)組里面有數(shù)據(jù)
    if (NULL != set) {
      CFTypeRef context[2] = {rl, rls};
      /* add new item to all common-modes */
      // 則把set里的所有 mode 都執(zhí)行一遍 __CFRunLoopAddItemToCommonModes 函數(shù)
      CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
      CFRelease(set);
    }
    // 以上分支的邏輯就是,如果你往 kCFRunLoopCommonModes 里面添加一個 source,那么所有 _commonModes 里面的 mode 都會添加這個 source
  } else {
    // 根據(jù) modeName 查找 mode
    CFRunLoopModeRef rlm = __CFRunLoopFIndMode(rl, modeName, true);
    // 如果 _sources0 不存在,則初始化 _sources0 和 _portToV1SourceMap
    if (NULL != rlm && NULL == rlm->_sources0) {
      rlm->sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
      rlm->sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBadks);
      rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
    }
    // 如果 _sources0 和 _source1 中都不包含傳入的 source
    if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
      // 如果 version 是 0,則加到 _sources0
      if (0 == rls->_context.version0.version) {
        CFSetAddValue(rlm->_sources0, rls);
      // 如果 version 是 1,則加到 _sources1
      } else if (1 == rls->_context.version0.version) {
        CFSetAddValue(rlm->_sources1, rls);
        __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
        if (CFPORT_NULL != src_port) {
          // 此處只有在加到 source1 的時候才會把 souce 和 一個 mach_port_t 對應(yīng)起來
          // 可以理解為,source1 可以通過內(nèi)核向其端口發(fā)送消息來主動喚醒 runloop
          CFDictionarySetValue(rlm->_portAToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
          __CFPortSetInsert(scr_port, rlm->_portSet);
        }
      }
      __CFRunLoopSourceLock(rls);
      // 把 runloop 加入到 source 的 _runLoops 中
      if (NULL == rls->_runLoops) {
        rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks);
      }
      CFBagAddValue(rls->_runLoops, rl);
      __CFRunLoopSourceUnlock(rls);
      if (0 == rls->_context.version0.version) {
        if (NULL != rls->_context.version0.schedule) {
          doVer0Callout = true;
        }
      }
    }
    if (NULL != rlm) {
      __CFRunLoopModeUnlock(rlm);
    }
  }
  __CFRunLoopUnlock(rl);
  if (doVer0Callout) {
    // although it looses some protection for the source, we have no choice but
    // to do this after unlocking the run loop and mode locks, to avoid deadlocks
    // where the source wants to take a lock which is already held in another
    // thread which is itself waiting for a run loop/mode lock
    rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
  }
}

通過添加 source 的這段代碼可以得出如下結(jié)論:

  • 如果 modeName 傳入 kCFRunLoopCommonModes, 則該 source 會被保存到 RunLoop 的 _commonModeItems 中
  • 如果 modeName 傳入 kCFRunLoopCommonModes, 則該 source 會被添加到所有 commonMode 中
  • 如果 modeName 傳入的不是 kCFRunLoopCommonModes,則會先查找該 Mode,如果沒有,會創(chuàng)建一個
  • 同一個 source 在一個 mode 中只能被添加一次
CFRunLoopRemoveSource

remove 操作和 add 操作的邏輯基本一致,很容易理解。

//移除source
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
    CHECK_FOR_FORK();
    Boolean doVer0Callout = false, doRLSRelease = false;
    __CFRunLoopLock(rl);
    //如果是kCFRunLoopCommonModes,則從_commonModes的所有mode中移除該source
    if (modeName == kCFRunLoopCommonModes) {
        if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) {
            CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
            CFSetRemoveValue(rl->_commonModeItems, rls);
            if (NULL != set) {
                CFTypeRef context[2] = {rl, rls};
                /* remove new item from all common-modes */
                CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
                CFRelease(set);
            }
        } else {
        }
    } else {
        //根據(jù)modeName查找mode,如果不存在,返回NULL
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
        if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) {
            CFRetain(rls);
            //根據(jù)source版本做對應(yīng)的remove操作
            if (1 == rls->_context.version0.version) {
                __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
                if (CFPORT_NULL != src_port) {
                    CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port);
                    __CFPortSetRemove(src_port, rlm->_portSet);
                }
            }
            CFSetRemoveValue(rlm->_sources0, rls);
            CFSetRemoveValue(rlm->_sources1, rls);
            __CFRunLoopSourceLock(rls);
            if (NULL != rls->_runLoops) {
                CFBagRemoveValue(rls->_runLoops, rl);
            }
            __CFRunLoopSourceUnlock(rls);
            if (0 == rls->_context.version0.version) {
                if (NULL != rls->_context.version0.cancel) {
                    doVer0Callout = true;
                }
            }
            doRLSRelease = true;
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    __CFRunLoopUnlock(rl);
    if (doVer0Callout) {
        // although it looses some protection for the source, we have no choice but
        // to do this after unlocking the run loop and mode locks, to avoid deadlocks
        // where the source wants to take a lock which is already held in another
        // thread which is itself waiting for a run loop/mode lock
        rls->_context.version0.cancel(rls->_context.version0.info, rl, modeName);   /* CALLOUT */
    }
    if (doRLSRelease) CFRelease(rls);
}
添加 Observer 和 Timer

添加 observer 和 timer 的內(nèi)部邏輯和添加 source 大體類似。

區(qū)別在于 observer 和 timer 只能被添加到一個 RunLoop 的一個或者多個 mode 中,比如一個 timer 被添加到主線程的 RunLoop 中,則不能再把該 timer 添加到子線程的 RunLoop,而 source 沒有這個限制,不管是哪個 RunLoop,只要 mode 中沒有,就可以添加。

這個區(qū)別在文章最開始的結(jié)構(gòu)體中也可以發(fā)現(xiàn),CFRunLoopSource 結(jié)構(gòu)體中有保存 RunLoop 對象的數(shù)組,而 CFRunLoopObserver 和 CFRunLoopTimer 只有單個 RunLoop 對象。

RunLoop 運(yùn)行

在 Core Foundation 中我們可以通過以下 2 個 API 來讓 RunLoop 運(yùn)行:

  • void CFRunLoopRun(void)
    在默認(rèn)的 mode 下運(yùn)行當(dāng)前線程的 RunLoop

  • CFRunLoopRunResult CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
    在指定 mode 下運(yùn)行當(dāng)前線程的 RunLoop

CFRunLoopRun

// 默認(rèn)運(yùn)行 runloop 的 kCFRunLoopDefaultMode
void CFRunLoopRun(void) { /* DOES CALLOUT */
  int32_t result;
  do {
    // 默認(rèn)在 KCFRunLoopDefaultMode 下運(yùn)行 runloop
    result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    CHECK_FOR_FORK();
  } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

在 CFRunLoopRun 函數(shù)中調(diào)用了 CFRunLoopRunSpecific 函數(shù),runloop 參數(shù)傳入當(dāng)前 RunLoop 對象,modeName 參數(shù)傳入 KCFRunLoopDefaultMode。驗(yàn)證了前面文檔的解釋。

CFRunLoopRunInMode

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
  CHECK_FOR_FORK();
  return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

在 CFRunLoopRunInMode 函數(shù)中也調(diào)用了 CFRunLoopRunSpecific 函數(shù),runloop 參數(shù)傳入當(dāng)前 RunLoop 對象,modeName 參數(shù)繼續(xù)傳遞 CFRunLoopRunInMode 傳入的 modeName。也驗(yàn)證了前文文檔的解釋。

這里還可以看出,雖然 RunLoop 有很多個 mode,但是 RunLoop 在 run 的時候必須只能指定其中一個 mode,運(yùn)行起來后,被指定的 mode 即為 currentMode。

這 2 個函數(shù)都看不出來 RunLoop 是怎么 run 起來的。

接下來我們繼續(xù)探索一下 CFRunLoopRunSpecific 函數(shù)里面都干了什么,看看 RunLoop 具體是怎么 run 的。

CFRunLoopRunSpecific

 * 指定 mode 運(yùn)行 runloop
 * @param rl 當(dāng)前運(yùn)行的 runloop
 * @param modeName 需要運(yùn)行的 mode 的 name
 * @param seconds  runloop 的超時時間
 * @param returnAfterSourceHandled 是否處理完事件就返回
 */
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;

  // 1.通知 observer 即將進(jìn)入 runloop
  if (currentMode->_observerMask & kCFRunLoopEntry) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
  result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
  // 10.通知 observer 已經(jīng)退出 runloop
  if (currentMode->_observerMask & kCFRunLoopExit) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  
  __CFRunLoopModeUnlock(currentMode);
  __CFRunLoopPopPerRunData(rl, previousPerRun);
  rl->_currentMode = previousMode;
  __CFRunLoopUnlock(rl);
  return result;
}

通過 CFRunLoopRunSpecific 的內(nèi)部邏輯,我們可以得出:

  • 如果指定一個不存在的 mode 來運(yùn)行 RunLoop,那么會失敗,mode 不會被創(chuàng)建,所有這里傳入的 mode 必須是存在的
  • 如果指定了一個 mode,但是這個 mode 中不包含人 modeItem,那么 RunLoop 也不會運(yùn)行,所以必須要傳入至少包含一個 modeItem 的 mode
  • 在進(jìn)入 run loop 之前通知 observer,狀態(tài)為 kCFRunLoopEntry
  • 在退出 run loop 之后通知 observer,狀態(tài)為 kCFRunLoopExit

RunLoop 的運(yùn)行的最核心函數(shù)是 __CFRunLoopRun,接下來我們分析 __CFRunLoopRun 的源碼。

__CFRunLoopRun

這段代碼比較長,請做好心理準(zhǔn)備,我已經(jīng)加了比較詳細(xì)的注釋。本節(jié)開頭的 run loop 運(yùn)行步驟 2~9 步都在下面的代碼中得到驗(yàn)證。

/**
 *  運(yùn)行run loop
 *
 *  @param rl              運(yùn)行的RunLoop對象
 *  @param rlm             運(yùn)行的mode
 *  @param seconds         run loop超時時間
 *  @param stopAfterHandle true:run loop處理完事件就退出  false:一直運(yùn)行直到超時或者被手動終止
 *  @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)啟動后的 CPU 運(yùn)行時間,用于控制超時時間
  uint64_t startTSR = mach_absolute_time();
  // 如果 RunLoop 或者 mode 是 stop 狀態(tài),則直接 return,不進(jìn)入循環(huá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;
  // 判斷是否為主線程
  Boolen 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_4FC(rlm->_queue);
    if (!modeQueuePort) {
      CRASH("Unable to get port for run loop mode queue (%d)", -1)
    }
  }
#endif
  // GCD 管理的定時器,用于實(shí)現(xiàn) runloop 的超時機(jī)制
  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;
  }
  // seconds 為超時時間,超時執(zhí)行 __CFRunLoopTimeout 函數(shù)
  else if (seconds <= TIMER_INTERVAL_LIMIT) {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_UEUE_PRIOITY_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(timout_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_IME_FOREVER, 1000ULL);
    dispatch_resume(timeout_timer);
  }
  // 永不超時
  else {
    seconds = 9999999999.0;
    timeout_context->termTSR = UNT64_MAX;
  }
  // 標(biāo)志位默認(rèn)為 true
  Boolean didDispatchPortLastTime = true;
  // 記錄最后 runloop 狀態(tài),用于 return
  int32_t reVal = 0;
  do {
    // 初始化一個存放內(nèi)核消息的緩沖地
    uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENAT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    match_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.通知 observer,即將觸發(fā) timer 回調(diào),處理 timer 事件
    if (rlm->_observerMask & kCFRunLoopBeforeTimes) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    // 3.通知 observer,即將觸發(fā) Source0 回調(diào)
    if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    // 執(zhí)行加入當(dāng)前 runloop 的 block
    __CFRunLoopDoBlocks(rl, rlm);

    // 4. 處理 source0 事件
    // 有事件處理返回 true,沒有事件返回 false
    Boolean sourceHandleThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    if (sourceHandledThisLoop) {
      // 執(zhí)行加入當(dāng)前 runloop 的 block
      __CFRunLoopDoBlocks(rl, rlm);
    }
    // 如果沒有 Sources0 事件處理并且沒有超市,poll 為 false
    Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
    // 第一次 do...while 循環(huán)不會走該分支,因?yà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. 接收 dispatchPort 端口消息,(接收 source1 事件)
      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. 通知觀察者 RunLoop 即將進(jìn)入休眠
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    // 設(shè)置 RunLoop 為休眠zhuangt
    __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_INFINTY);
      // 收到消息之后,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) {
          rlm->_timerFired = false;
          break;
        } else {
          if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        }
      } else {
        break;
      }
    } while (1);
#else
    if (kCFUseCollectableAllocator) {
      objc_clear_stack(0);
      memset(msg_buffer, 0, sizeof(msg_buffer));
    }
    msg = (mach_msg_header_t *)msg_buffer;
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0: TIMEOUT_INFINITY);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
    // hear, use the app-suplied 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, &windowsMessageRecieved);
#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);
    // 取消 runloop 的休眠狀態(tài)
    __CFRunLoopUnsetSleeping(rl);
    // 8.通知觀察者 runloop 被喚醒
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
      __CFRunLoopDoObserver(rl, rlm, kCFRunLoopAfterWaiting);
    // 9.處理收到的消息
handle_msg:;
    __CFRunLoopSetIgnoreWakeUps(rl);
#if DELOYMENT_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(rml);
      __CFRunLoopUnsetSleeping(rl);
    }
#endif
    if (MACH_PORT_NULL == livePort) {
      CFRUNLOOP_WAKEUP_FOR_NOTHING();
      // 通過 CFRunLoopWake 喚醒
    } else if (livePort == rl->_wakeUpPort) {
      // 什么都不干,跳回 2 重新循環(huán)
#if DEPLOYMENT_TARGET_WINDOWS
      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 事件
      if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
        __CFArmNextATimerInMode(rlm, rl);
      }
    }
#endif
#if USE_MK_TIMER_TOO
    // 如果是定時器事件
    else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
      CFRUNLOOP_WAKEUP_FOR_TIMER();
      // 9.1 處理 timer 事件
      if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
        __CFArmNextTimerInMode(rlm, rl);
      }
    }
#endif
    // 如果是 dispatch 到 main queue 的 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
      // 9.2 執(zhí)行 block
      __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
      _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
      __CFRunLoopLock(rl);
      __CFRunLoopModeLock(rlm);
      sourceHandledThisLoop = true;
      didDispatchPortLastTime = true;
    } else {
      CFRUNLOOP_WAKEUP_FOR_SOURCE();
      CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
      // 有 source1 事件待處理
      if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *reply = NULL;
        // 9.2 處理 source1 事件
        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 DEPLOYMENTA_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
    __CFRunLoopDoBlocks(rl, rlm);
    if (sourceHandledThisLoop && stopAfterHandle) {
      // 進(jìn)入 run loop 時傳入的參數(shù),處理完事件就返回
      retVal = kCFRunLoopRunHandledSource;
    } else if (timeout_context->termTSR < mach_absolute_time()) {
      // run loop 超時
      retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) {
      // run loop 被手動終止
      __CFRunLoopUnsetStopped(rl);
      retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
      // mode 被終止
      rlm->_stopped = false;
      retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
      // mode 中沒有要處理的事件
      retVal = kCFRunLoopRunFinished;
    }
    // 除了上面這幾種情況,都繼續(xù)循環(huán)
  } while (0 == retVal);
  if (timeout_timer) {
    dispatch_source_cancel(timeout_timer);
    dispatch_release(timeout_timer);
  } else {
    free(timeout_context);
  }
  return retVal;
}

__CFRunLoopServiceMachPort

第7步調(diào)用了 __CFRunLoopServiceMachPort 函數(shù),這個函數(shù)在 run loop 中起到了至關(guān)重要的作用,下面給出了詳細(xì)注釋。

/**
 *  接收指定內(nèi)核端口的消息
 *
 *  @param port        接收消息的端口
 *  @param buffer      消息緩沖區(qū)
 *  @param buffer_size 消息緩沖區(qū)大小
 *  @param livePort    暫且理解為活動的端口,接收消息成功時候值為msg->msgh_local_port,超時時為MACH_PORT_NULL
 *  @param timeout     超時時間,單位是ms,如果超時,則RunLoop進(jìn)入休眠狀態(tài)
 *
 *  @return 接收消息成功時返回true 其他情況返回false
 */
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {      /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;  //消息頭的標(biāo)志位
        msg->msgh_local_port = port;  //源(發(fā)出的消息)或者目標(biāo)(接收的消息)
        msg->msgh_remote_port = MACH_PORT_NULL; //目標(biāo)(發(fā)出的消息)或者源(接收的消息)
        msg->msgh_size = buffer_size;  //消息緩沖區(qū)大小,單位是字節(jié)
        msg->msgh_id = 0;  //唯一id
       
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        
        //通過mach_msg發(fā)送或者接收的消息都是指針,
        //如果直接發(fā)送或者接收消息體,會頻繁進(jìn)行內(nèi)存復(fù)制,損耗性能
        //所以XNU使用了單一內(nèi)核的方式來解決該問題,所有內(nèi)核組件都共享同一個地址空間,因此傳遞消息時候只需要傳遞消息的指針
        ret = mach_msg(msg,
                       MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
                       0,
                       msg->msgh_size,
                       port,
                       timeout,
                       MACH_PORT_NULL);
        CFRUNLOOP_WAKEUP(ret);
        
        //接收/發(fā)送消息成功,給livePort賦值為msgh_local_port
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        
        //MACH_RCV_TIMEOUT
        //超出timeout時間沒有收到消息,返回MACH_RCV_TIMED_OUT
        //此時釋放緩沖區(qū),把livePort賦值為MACH_PORT_NULL
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        
        //MACH_RCV_LARGE
        //如果接收緩沖區(qū)太小,則將過大的消息放在隊(duì)列中,并且出錯返回MACH_RCV_TOO_LARGE,
        //這種情況下,只返回消息頭,調(diào)用者可以分配更多的內(nèi)存
        if (MACH_RCV_TOO_LARGE != ret) break;
        //此處給buffer分配更大內(nèi)存
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

小結(jié)

RunLoop 實(shí)際很簡單,它是一個對象,它和線程是一一對應(yīng)的,每個線程都有一個對應(yīng)的 RunLoop 對象,主線程的 RunLoop 會在程序啟動時自動創(chuàng)建,子線程需要手動獲取來創(chuàng)建。

RunLoop 運(yùn)行的核心是一個 do..while.. 循環(huán),遍歷所有需要處理的事件,如果有事件處理就讓線程工作,沒有事件處理則讓線程休眠,同時等待事件到來。

?著作權(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)容

  • 轉(zhuǎn)自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_閱讀 1,689評論 0 5
  • RunLoop 的概念 一般來講,一個線程一次只能執(zhí)行一個任務(wù),執(zhí)行完成后線程就會退出。如果我們需要一個機(jī)制,讓線...
    Mirsiter_魏閱讀 679評論 0 2
  • RunLoop簡介 從字面意思來看是運(yùn)行循環(huán),在程序運(yùn)行過程中循環(huán)做一些事情,如果沒有Runloop程序執(zhí)行完畢就...
    一直很安靜_25ae閱讀 458評論 0 0
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,561評論 0 13
  • RunLoop的概念 一般來講,一個線程一次只能執(zhí)行一個任務(wù),執(zhí)行完成后線程就會退出。如果我們需要一個機(jī)制,讓線程...
    IOS學(xué)渣閱讀 498評論 1 4

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