貳:RunLoop的內部結構
RunLoopMode
iOSRunLoop一共有幾個核心的類,分別是:
- CFRunLoop
- CFRunLoopMode
- CFRunLoopSource
- CFRunLoopObserver
- CFRunLoopTimer
RunLoopMode定義
以下是原始碼中的關於RunLoop以及RunLoopMode的部分定義:
```c struct CFRunLoop { ... pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; ... }
struct CFRunLoopMode { ... CFMutableSetRef _source0; CFMutableSetRef _source1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; ... } ```
根據上面的RunLoop以及RunLoopMode的定義,我們可以得出以下的對應關係:一個RunLoop可以包含有多個Mode,一個mode可以包含多個Source0 & Source1 & Observer & Timer。
每次呼叫RunLoop的主函式的時候,只能指定其中一個Mode,這個Mode就被稱問Common Mode。如果需要切換Mode, 只能退出RunLoop重新指定Mode進入。這樣做是為了分割開不同組別的Source/Timer/Observer, 讓其互不影響。RunLoop一次只在一個Mode下執行。
CFRunLoopSourceRef
Source是RunLoop的資料來源(input source)的一個抽象類,Source有兩個版本:Source 0和Source 1。
```c struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; pthread_mutex_t _lock; CFIndex _order; / immutable / CFMutableBagRef _runLoops;
// 共用體
// 同一時刻,共用體只存放了一個被選擇的成員
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
}; ```
通過version的不同來區分兩種不同型別的Source,而這兩種source也有著很明顯的區別。
Source0
Souece0包含了一個回撥(函式指標),它並不能直接喚醒RunLoop,只能去主動去呼叫方法來喚醒RunLoop。如果我們自己建立來一個Source0,想通過Source0來喚醒RunLoop,那麼需要兩個步驟:
- 傳送訊號:
CFRunLoopSourceSignal(rs)
- 手動喚醒:
CFRunLoopWakeUp(RunLoop.main.getCFRunLoop())
也就是說我們需要去呼叫CFRunLoopSourceSignal(source), 來標記這個Source正在等待處理,然後需要手動呼叫CFRunLoopWakeUp(runloop)來喚醒RunLoop,讓它來處理這個事件。
Source1
它被RunLoop和kernel管理,通過mach_port來驅動(特指基於port的事件),比如CFMachPort, CFMessagePort,NSSocketPort。Mach Port是很輕量級的方式用於程序之間的通訊,它可以被理解為一個通訊的channel。比如點觸事件,其實就是由BackService直接將IOEvent傳遞給處理該事件的前臺程序。這就涉及到了程序間的通訊,使用的就是Source1。
CFRunLoopTimerRef
基於時間的觸發器,它和NSTimer是toll-free bridged的,可以混用(它的底層是基於mk_timer的)。它是會被RunLoop Mode所影響的(而GCD的timer是不會被RunLoopMode影響的)。當它被加入RunLoop之後,RunLoop將會註冊與之對應的時間點,當時間點到達的時候,Runloop將被喚醒來執行回撥,如果這個執行緒被阻塞了或者此時RunLoop不在這個Mode中,那麼這個觸發的時間點點並不會執行回撥,將等到下一次迴圈的時間點被觸發。
CFRunLoopObserverRef
它是觀察者,每個Observer都包含了對應一個回撥(函式指標),一個Mode中可以有多個觀察者,當RunLoop的狀態發生改變時,觀察者就可以通過回撥接受這個變化。可以觀測的時間點有以下幾個:
c
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
這些source & timer & observer都統一被稱為mode item,一個item可以被同時加入多個mode。但是一個item被重複加入同一個mode時是不會有效果的。如果一個mode中一個item都沒有,那麼RunLoop就會直接退出,不進入迴圈。
Common Modes
同時要注意有一個概念叫做Common Mode。一個Mode可以把自己標記為Common(將mode新增到CommonMode中)。
swift
// 當然Mode中需要加observer/Timer/source
let mode = CFRunLoopMode.init(rawValue: "fff" as CFString)
CFRunLoopAddCommonMode(RunLoop.main.getCFRunLoop(), mode)
這樣的話,每當RunLoop的內容變化時(切換Mode執行時),RunLoop會自動將commonModeItems中的Source & Observer & Timer同步到具有"Common"標記的所有Mode中。
舉個例子:主執行緒的RunLoop中有兩個預先設定的Mode,其一是kCFRunLoopDefaultMode, 其二是UITrackingRunLoopMode。這兩個Mode都被標記為Common屬性。DefaultMode是預設狀態,TrackingMode是追蹤ScrollView滑動時的狀態。當建立一個Timer時,Timer會得到重複的回撥。但是當滑動一個UIScrollView時,RunLoop會將Mode切換都TrackingMode,這個時候Timer的回撥就不會生效了。
所以需要這個Timer在兩個Mode中都可以得到回撥,那麼其中一種方式就是將這個Timer分別放入到這兩個Mode中。還有一種方式,就是將Timer放到頂層的RunLoop的CommonModeItems中,這其中的items都會被自動更新到所有具備Common屬性的Mode中。
Mode的分類
從Apple的官方文件中,我們可知其實系統提供了幾個Mode:
- kCFRunLoopDefaultMode: App的預設Mode,通常主執行緒是在這個Mode下執行的。
- UITrackingRunLoopMode:介面追蹤Mode,用於ScrollView的追蹤,保證介面滑動不受影響
- UIInitializationRunLoopMode:剛啟動App時進入的第一個Mode,啟動後不再使用
- GSEventReceiveRunLoopMode:接受系統事件的內部Mode,通常用不到
- KCFRunLoopCommonModes:佔位的Mode
Mode例項
直接將當前執行緒的RunLoop在控制檯輸出,可以得到如下的結構:
```c CFRunLoop { current mode = KCFRunLoopDefaultMode, common modes = { UITrackingRunLoopMode, KCFRunLoopDefaultMode }
common mode items = {
//source 0
CFRunLoopSource = { order = -1,{
callout = PurpleEventSignalCallback}} //GraphicsServices
CFRunLoopSource = { order = 0,{
callout = HandleDelegateSource(void*)}} //WebCore
CFRunLoopSource = { order = 0, {
callout = __NSThreadPerformPerform}} //Foundation
CFRunLoopSource = { order = 0, {
callout = FBSSerialQueueRunLoopSourceHandler}} // FrontBoardServices
CFRunLoopSource = { order = -1,{
callout = __eventQueueSourceCallback}} //UIKitCore
CFRunLoopSource = { order = 0,{
callout = WTF::RunLoop::performWork(void*)}} //JavaScriptCore
CFRunLoopSource = { order = -2,{
callout = __eventFetcherSourceCallback}} //UIKitCore
// source 1(mach port)
CFRunLoopSource {order = 0, {port = 17923}}
CFRunLoopSource {order = 0, {port = 12039}}
CFRunLoopSource {order = 0, {port = 16647}}
CFRunLoopSource {order =-1, {
callout = PurpleEventCallback}}
CFRunLoopSource {order = 0, {port = 2407,
callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
CFRunLoopSource {order = 0, {port = 1c03,
callout = __IOHIDEventSystemClientAvailabilityCallback}}
CFRunLoopSource {order = 0, {port = 1b03,
callout = __IOHIDEventSystemClientQueueCallback}}
CFRunLoopSource {order = 1, {port = 1903,
callout = __IOMIGMachPortPortCallback}}
// observer
CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
callout = _wrapRunLoopWithAutoreleasePoolHandler}
CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting
callout = _UIGestureRecognizerUpdateObserver}
CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit
callout = _beforeCACommitHandler}// UIKitCore
CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit
callout = _afterCACommitHandler} // UIKitCore
CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit
callout = CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*)}
CFRunLoopObserver {order = -2147483648, activities = 0x46,// BeforeTimer | BeforeSource | AfterWait
callout = __trackRunLoopTimes} // UIKitCore
CFRunLoopObserver {order = 2147483647, activities = 0x20, // BeforeWaiting
callout = __trackRunLoopTimes} // UIKitCore
CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
callout = _wrapRunLoopWithAutoreleasePoolHandler}
// timer
CFRunLoopTimer {valid = Yes, firing = No, interval = 3.1536e+09, tolerance = 0,
next fire date = 1.17402502e+09 (504911209 @ 12130246606742925),
callout = CA::timer_callback(__CFRunLoopTimer*, void*)} // QuartzCore
CFRunLoopTimer {valid = Yes, firing = No, interval = 3, tolerance = 0,
next fire date = 669113787 (-20.825485 @ 12377085760065),
callout = [FSPagerView.FSPagerView flipNextWithSender:]}
}
modes = {
CFRunLoopMode { //kCFRunLoopDefaultMode
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode { //UITrackingRunLoopMode
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode { // kCFRunLoopCommonModes
source0 = null,
source1 = null,
timers = null,
observers = null,
},
CFRunLoopMode { // GSEventReceiveRunLoopMode
source0 = {
CFRunLoopSource {order = 1,
callout = PurpleEventSignalCallback}
},
source1 = {},
observers = null,
timers = null,
}
}
}
} ```
我們可以很明確的看到,一個RunLoop下對應有很多種不同的Mode,而每個Mode中都有對應的source,observer等元素。當然要想看到更多的Mode型別的話,可以到Apple關於RunLoop的官方描述中檢視。
RunLoop的結構
接下來我直接PO一段RunLoop的原始碼:
```c // GitHub上開源的CoreFoundation,run方法其實還是呼叫了執行defaultMode的方法 extension RunLoop { public func run() { while run(mode: .default, before: Date.distantFuture) { } } ... }
// 使用指定的模式來啟動RunLoop
int CFRunInMode(modeName, seconds, returnAftersourceHandled) {
return CFRunloopRunSpecific(CFRunLoopGetCurrent(),
modeName,
seconds,
returnAftersourceHandled);
}
int CFRunloopRunSpecific(runloop, modeName, seconds, returnAftersourceHandled) { // 根據mode名找到對應的mode CFRunLoopModeRef currentMode = __CFRunLoopMode(r1, modeName, false);
if (Null == currentMode || CFRunLoopModeIsEmpty(currentMode)) {
return;
}
int result = KCFRunLoopRunFinished;
// 1、通知observers:RunLoop即將進入RunLoop (KCFRunLoopEntry)
CFRunLoopDoObservers(rl, currentMode, KCFRunLoopEntry);
result = __CFRunLoopRun(runloop, currentMode, ...);
// 內部函式
int __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled) {
bool didDispatchPortLastTime = true;
in retVal = 0;
do {
// runloopMode中的所有port
__CFPortSet waitSet = rlm->_portSet;
// 2、通知observers:RunLoop即將準備處理timer
CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 3、通知observers:RunLoop即將準備處理source(不基於port的即source0)
CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// -- 執行被加入的block
CFRunLoopDoBlocks(rl, rlm);
// 4、處理Source0:Runloop處理source(不基於port的source)
bool sourceHandledThisLoop = CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// -- 執行被加入的block
if (sourceHandledThisLoop) {
CFRunLoopDoBlocks(rl, rlm);
}
// 5、如果有Source1處於Ready狀態:RunLoop跳到第九步處理Source(基於port的source)
// 這裡指定了接收訊息的埠是dispatchPort:如Dispatch.main.async{}
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
bool hasMsg = CFRunLoopServiceMachPort(dispatchPort, msg, &livePort, 0);
if (hasMsg) goto handle_msg;
}
didDispatchPortLastTime = false;
// 6、通知Observers:RunLoop即將進入休眠(kCFRunLoopBeforeWaiting)
CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 7、執行緒進入睡眠:呼叫CFRunLoopServiceMachPort等待被喚醒,被以下幾個喚醒
// * 一個基於port的source事件(·)
// * 一個到點要執行的timer(Timer)
// * RunLoop要超時了(RunLoopTimeOut)
// * RunLoop被顯式的喚醒了
CFRunLoopServiceMachPort(waitSet, msg, livePort, TIMEOUT_INFINITY);
// 8、通知Observers:執行緒被喚醒了
CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 9、收到訊息,處理訊息。
handle_msg;
// * 無port啥也不做
if (Mach_Port_Null == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// * 標記為喚醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// * 標記為Timer喚醒:Timer/NSTimer喚醒
} else if (livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!_CFRunLoopDoTimers(rl, rlm, mach_absolute_timer())) {
CFArmNextTimerInMode(rlm, rl);
}
// * 標記為GCD喚醒:由main_queue派發的block
} else if (livPort === dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
// * 標記為Source1喚醒
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 執行加入到runloop中的block
CFRunLoopDoBlocks(rl, rlm);
// 判斷runloop是否結束
// * 剛剛處理完事件
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
// * runloop超時了
} else if (timeout) {
retVal = kCFRunLoopRunTimedOut;
// * 被外部呼叫者強制停止了
} else if (CFRunLoopIsStopped(rl)) {
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
// * runloop的Mode中source/timer/observer一個都沒有了
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retValue);
}
//10、通知Observers:RunLoop即將離開
CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
} ```
它的內部邏輯是一個while迴圈,可以一直使執行緒處於存活的狀態,有事情來就處理事情,沒有事情來,就處於休息狀態。基於上方的流程,我也建立了一個RunLoop流程圖。
根據上述的流程,我們可以梳理出相應的回撥:
```c { /// 1. 通知Observers,即將進入RunLoop /// 此處有Observer會建立AutoreleasePool: _objc_autoreleasePoolPush(); CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION(kCFRunLoopEntry); do {
/// 2. 通知 Observers: 即將觸發 Timer 回撥。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即將觸發 Source (非基於port的,Source0) 回撥。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 觸發 Source0 (非基於port的) 回撥。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即將進入休眠
/// 此處有Observer釋放並新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. 通知Observers,執行緒被喚醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. 如果是被Timer喚醒的,回撥Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch喚醒的,執行所有呼叫 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. 如果如果Runloop是被 Source1 (基於port的) 的事件喚醒了,處理這個事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
/// 10. 通知Observers,即將退出RunLoop
/// 此處有Observer釋放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
} ```
- 我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿。