深刻淺出 RunLoop(一):初識
深刻淺出 RunLoop(二):數據結構
深刻淺出 RunLoop(三):事件循環機制
深刻淺出 RunLoop(四):RunLoop 與線程
深刻淺出 RunLoop(五):RunLoop 與 NSTimer
深刻淺出 RunLoop(六):相關面試題面試
前言
前面咱們介紹了
RunLoop
的基本概念以及相關數據結構,這篇咱們來說解一下RunLoop
究竟是怎麼工做的。數據結構
首先咱們來看一下主線程的RunLoop
的啓動過程。
前面咱們說過,咱們的 iOS 程序能保持持續運行的緣由就是在main()
函數中調用了UIApplicationMain
函數,這個函數內部會啓動主線程的RunLoop
。
打斷點,經過 LLDB 指令bt
查看函數調用棧以下: app
UIApplicationMain
函數中調用了 Core Foundation 框架下的
CFRunLoopRunSpecific
函數。
查看源碼中該函數的實現,以下:框架
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根據 modeName 找到本次運行的 mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 若是沒找到 || mode 中沒有註冊任何事件,則就此中止,不進入循環
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
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// RunLoop 具體要作的事情
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;
}
複製代碼
刪掉不重要的細節:async
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知 Observers:即將進入 RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// RunLoop 具體要作的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知 Observers:即將退出 RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
複製代碼
從函數調用棧,以及CFRunLoopRunSpecific
函數的實現中能夠得知,RunLoop
事件循環的實現機制體如今__CFRunLoopRun
函數中。 函數
因爲該函數實現較複雜,如下爲刪掉細節的精簡版本,想探究具體的能夠查看 Core Foundation 源碼。oop
/** * __CFRunLoopRun * * @param rl 運行的 RunLoop 對象 * @param rlm 運行的 mode * @param seconds loop 超時時間 * @param stopAfterHandle true: RunLoop 處理完事件就退出 false:一直運行直到超時或者被手動終止 * @param previousMode 上一次運行的 mode * * @return 返回 4 種狀態 */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
int32_t retVal = 0;
do {
// 通知 Observers:即將處理 Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 Observers:即將處理 Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 處理 Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 處理 Sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
// 處理 Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 判斷有無 Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// 若是有 Source1,就跳轉到 handle_msg
goto handle_msg;
}
// 通知 Observers:即將進入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// ⚠️休眠,等待消息來喚醒線程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
__CFRunLoopUnsetSleeping(rl);
// 通知 Observers:剛從休眠中喚醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被 Timer 喚醒) {
// 處理 Timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
} else if (被 GCD 喚醒) {
// 處理 GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被 Source1 喚醒
// 處理 Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 處理 Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 設置返回值
if (sourceHandledThisLoop && stopAfterHandle) { // 進入 loop 時參數爲處理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) { // 超出傳入參數標記的超時時間
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) { // 被外部調用者強制中止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) { // 自動中止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { // mode 中沒有任何的 Source0/Source1/Timer/Observer
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
複製代碼
從該函數實現中能夠得知RunLoop
主要就作如下幾件事情:post
Observers
接下來要作什麼Blocks
Sources0
Sources1
Timers
dispatch_async(dispatch_get_main_queue(), ^{ });
在__CFRunLoopRun
函數中,會調用__CFRunLoopServiceMachPort
函數,該函數中調用了mach_msg()
函數來轉移當前線程的控制權給內核態/用戶態。ui
mach_msg()
從用戶態切換到內核態,等待消息;mach_msg()
回到用戶態處理消。這就是RunLoop
休眠的實現原理,也是RunLoop
與簡單的do...while
循環區別:this
RunLoop
:休眠的時候,當前線程不會作任何事,CPU 不會再分配資源;do...while
循環:當前線程並無休息,一直佔用 CPU 資源。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, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
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;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
// ⚠️⚠️⚠️
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|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);
// Take care of all voucher-related work right after mach_msg.
// If we don't release the previous voucher we're going to leak it.
voucher_mach_msg_revert(*voucherState);
// Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
*voucherState = voucher_mach_msg_adopt(msg);
if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
// Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
// CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
*voucherCopy = voucher_copy();
} else {
*voucherCopy = NULL;
}
}
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
複製代碼