RunLoop原理以及使用場景
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());
}
- 線程剛創建時並沒有RunLoop對象,RunLoop會在第一次獲取它時創建
- RunLoop會在線程結束時銷燬
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種
- kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
- UIInitializationRunLoopMode:在剛啟動 App 時第進入的第一個 Mode,啟動完成後就不再使用
- GSEventReceiveRunLoopMode:接受系統事件的內部 Mode,通常用不到
- 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
}; ```
runloop 運行邏輯
- CFMutableSetRef _sources0;
- 觸摸事件
- perform selectors
- CFMutableSetRef _sources1; 1. 基於port的線程間通訊 - CFMutableArrayRef _observers; 1. 監聽器 2. 用於監聽runloop狀態變化
- CFMutableArrayRef _timers; 1. 定時器, NSTimer
核心代碼解析(簡化版)
``` 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); } ``` 底層運行邏輯流程圖
RunLoop應用場景
- 控制線程生命週期(線程保活)
- 解決NSTimer在滑動時停止工作的問題
- 監控應用卡頓
- 性能優化
- 閃退處理