深刻淺出 RunLoop(一):初識
深刻淺出 RunLoop(二):數據結構
深刻淺出 RunLoop(三):事件循環機制
深刻淺出 RunLoop(四):RunLoop 與線程
深刻淺出 RunLoop(五):RunLoop 與 NSTimer
iOS - 聊聊 autorelease 和 @autoreleasepool:RunLoop 與 @autoreleasepoolmarkdown
前面咱們介紹了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); /* 設置返回值 */ // 進入 loop 時參數爲處理完事件就返回 if (sourceHandledThisLoop && stopAfterHandle) { 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; // mode 中沒有任何的 Source0/Source1/Timer/Observer } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; } } while (0 == retVal); return retVal; } 複製代碼
從該函數實現中能夠得知RunLoop
主要就作如下幾件事情:post
Observers
接下來要作什麼Blocks
Sources0
Sources1
Timers
dispatch_async(dispatch_get_main_queue(), ^{ });
在__CFRunLoopRun
函數中,會調用__CFRunLoopServiceMachPort
函數,該函數中調用了mach_msg()
函數來轉移當前線程的控制權給內核態/用戶態。this
mach_msg()
從用戶態切換到內核態,等待消息;mach_msg()
回到用戶態處理消息。這就是RunLoop
休眠的實現原理,也是RunLoop
與簡單的do...while
循環區別:spa
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; } 複製代碼