RunLoop原理以及使用場景

語言: CN / TW / HK

RunLoop學習記錄

什麼是RunLoop

運行循環,保持程序的持續運行,不退出,處理APP的跟中事件 runloop源碼下載地址:http://opensource.apple.com/tarballs/CF/

## 基本作用 - 保持程序的持續運行, - 處理APP中的各種事件(觸摸,定時器,GCD異步回到主線程,runloop中block回調的處理等) - 節省CPU資源,提供性能,改做事的時候做事,改休息的時候休息 ## RunLoop對象 iOS中提供了2套Api來訪問runlopp 1. Fundation : NSRunLoop [NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象 [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象 2. Core Fundation : CFRunloop

CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象 CFRunLoopGetMain(); // 獲得主線程的RunLoop對象 NSRunLoop是基於CFRunloop的一層OC包裝 ## RunLoop與線程 - 每條線程都有唯一的一個與之對應的RunLoop對象 - RunLoop保存在一個全局的Dictionary裏,線程作為key,RunLoop作為value CFRunLoopRef CFRunLoopGetCurrent(void) {     CHECK_FOR_FORK();     CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);     if (rl) return rl;     return _CFRunLoopGet0(pthread_self()); } image.png - 線程剛創建時並沒有RunLoop對象,RunLoop會在第一次獲取它時創建

image.png

  • RunLoop會在線程結束時銷燬

image.png

RunLoop相關的類

Core Foundation中關於RunLoop的5個類

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoop 的結構體

struct __CFRunLoop { pthread_t _pthread; // runloop和線程是一一對應關係,每個runloop內部會保留一個對應的線程 CFMutableSetRef _commonModes; //標記為common的mode的集合 CFMutableSetRef _commonModeItems; //commonMode的item集合 CFRunLoopModeRef _currentMode; // 當前的模式 CFMutableSetRef _modes; // CFRunLoopModeRef類型的集合,相對NSArray有序,Set為無序集合 };

CFRunLoopMode

  • CFRunLoopModeRef代表RunLoop的運行模式
  • 一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer
  • RunLoop啟動時只能選擇其中一個Mode,作為currentMode
  • 如果需要切換Mode,只能退出當前Loop,再重新選擇一個Mode進入,是為了解決不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
  • 如果Mode裏沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
目前已知的Mode有5種
  1. kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
  2. UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
  3. UIInitializationRunLoopMode:在剛啟動 App 時第進入的第一個 Mode,啟動完成後就不再使用
  4. GSEventReceiveRunLoopMode:接受系統事件的內部 Mode,通常用不到
  5. kCFRunLoopCommonModes:這是一個佔位用的Mode,不是一種真正的Mode

``` typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode {

CFMutableSetRef _sources0;

CFMutableSetRef _sources1;

CFMutableArrayRef _observers;

CFMutableArrayRef _timers;

CFMutableDictionaryRef _portToV1SourceMap;

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 USE_MK_TIMER_TOO

mach_port_t _timerPort;     Boolean _mkTimerArmed;

endif

}; ```

image.png

runloop 運行邏輯
  • CFMutableSetRef _sources0;
  • 觸摸事件
  • perform selectors

image.png

image.png - CFMutableSetRef _sources1;   1. 基於port的線程間通訊 - CFMutableArrayRef _observers; 1. 監聽器 2. 用於監聽runloop狀態變化

image.png

image.png - CFMutableArrayRef _timers; 1. 定時器, NSTimer image.png

核心代碼解析(簡化版)

``` 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 (runloop有個方法可以直接處理block)       __CFRunLoopDoBlocks(rl, rlm);        //  處理Sources0         Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);         if (sourceHandledThisLoop) {             __CFRunLoopDoBlocks(rl, rlm);     }         // 如果有source1 就跳轉到handle_msg標記處         if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {             goto handle_msg;         }         // 通知Observers: 即將休眠      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);        // 進入休眠 等待其他消息喚醒      __CFRunLoopSetSleeping(rl);      __CFPortSetInsert(dispatchPort, waitSet);      do {         __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);      } while (1);         // 醒來了         __CFPortSetRemove(dispatchPort, waitSet);         __CFRunLoopUnsetSleeping(rl);         // 通知Observers: 已經喚醒        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);         handle_msg:; // 看誰喚醒runloop ,進行響應處理        // 被timer喚醒         if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {             __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())         }else if (livePort == dispatchPort) { // 被GCD喚醒             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(msg);         } else { // 被source1喚醒            __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;         }         // 執行blocks        __CFRunLoopDoBlocks(rl, rlm);         // 根據之前的處理結果來決定返回值     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;     } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode))         retVal = kCFRunLoopRunFinished;     }         voucher_mach_msg_revert(voucherState);         os_release(voucherCopy);     } while (0 == retVal);

if (timeout_timer) {         dispatch_source_cancel(timeout_timer);         dispatch_release(timeout_timer);     } else {         free(timeout_context);     }     return retVal; } SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     // 通知Observers: 進入loop    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);     // 核心邏輯    __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);     // 通知Observers: 退出loop   __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);     return result; } void CFRunLoopRun(void) {    / DOES CALLOUT /     int32_t result;     do {         result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);         CHECK_FOR_FORK();     } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } ``` 底層運行邏輯流程圖 image.png

RunLoop應用場景

  • 控制線程生命週期(線程保活)
  • 解決NSTimer在滑動時停止工作的問題
  • 監控應用卡頓
  • 性能優化
  • 閃退處理