字面意思運行循環
,它是一個對象,這個對象提供一個入口函數。 程序會進入do...while
循環,處理事件。它不是一個普通的do-while
循環,普通的do-while
會一直暫用CPU資源,runloop
在沒有消息處理時,會進入休眠表面資源佔用。ios
CFRunLoopGetMain()
:獲取主運行循環。 CFRunLoopGetCurrent()
:獲取當前運行循環。相關源碼分析: 從獲取線程RunLoop
的方法CFRunLoopGetCurrent()
進去:macos
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
複製代碼
獲取RunLoop
,調用_CFRunLoopGet0
,當前線程(pthread_self()
做爲參數傳入。安全
static pthread_t kNilPthreadT = { nil, nil };
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//1.爲Nil,設置爲主線程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
2.加鎖,保證線程安全
__CFSpinLock(&loopsLock);
3.__CFRunLoops是CFMutableDictionaryRef類型的靜態全局變量,保存線程和runloop一一對應的關係
if (!__CFRunLoops) {
4.若是__CFRunLoops爲空
__CFSpinUnlock(&loopsLock);
//5.建立可變字典CFMutableDictionaryRef
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//6.經過pthread_main_thread_np()建立一個CFRunLoopRef
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//7.經過key-value的方式,將pthread_main_thread_np()和mainLoop存入`dict`
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//8.將dict賦值給__CFRunLoops
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
//9.在__CFRunLoops,線程做爲key獲取runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
//10.不存在runloop
if (!loop) {
//11.建立一個loop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//12.將建立好的newloop存儲到__CFRunLoops
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; } 複製代碼
__CFRunLoops
,線程爲key,對應的runloop
爲value
保存在__CFRunLoops
,線程和runloop是一一對應的關係.struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;//當前線程
uint32_t _winthread;
CFMutableSetRef _commonModes;//commonModes下的兩個mode(kCFRunloopDefaultMode和UITrackingMode)
CFMutableSetRef _commonModeItems;// 在commonModes狀態下運行的對象(例如Timer)
CFRunLoopModeRef _currentMode;////在當前loop下運行的mode
CFMutableSetRef _modes;// // 運行的全部模式(CFRunloopModeRef類)
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
複製代碼
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
複製代碼
一個RunLoop
對象中可能包含多個Mode
,且每次調用 RunLoop
的主函數時,只能指定其中一個 Mode(CurrentMode)
。可重寫指定並切換Mode
。主要是爲了分隔開不一樣的 Source、Timer、Observer
,讓它們之間互不影響。bash
RunLoop下共有五種mode:app
項目中,以下以下場景:頁面中有一個無限循環的banner,當用戶在界面上滑動時,banner定時器不起做用。 緣由:主線程的 RunLoop
裏有兩個 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。默認狀況下是defaultMode
,可是當滑動UIScrollView
時,RunLoop
會將 mode
切換爲 TrackingRunLoopMode
,這時 Timer 就不會執行。若是想在滑動的時候不讓定時器失效,可使用CommonMode
來解決。框架
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼
CFRunLoopTimerRef 是定時源,你能夠簡單把它理解爲NSTimer。其包含一個時間點和一個回調(函數指針)。當被加入到 RunLoop 時,RunLoop 會註冊對應的時間點,當時間到時,RunLoop 會執行對應時間點的回調。NSTimer 和 performSelector:withObject:afterDelay: 都是經過其處理的。異步
CFRunLoopObserverRef是觀察者,主要用來監聽RunLoop 的狀態,主要有如下幾種狀態。async
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
複製代碼
Runloop
的運行從CFRunLoopRun
開始.ide
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
複製代碼
接下來都是調用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);
//取上一次運行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//若是本次mode和上次的mode一致
rl->_currentMode = currentMode;
//初始化一個result爲kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry )
/// 1. 通知 Observers: RunLoop 即將進入 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit )
/// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
複製代碼
進入核心代碼__CFRunLoopRun
,代碼太長,這裏只貼出核心代碼:
/// 2. 通知 Observers: RunLoop 即將觸發 Timer 回調。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources)
/// 3. 通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調。
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
/// 執行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
/// 4. RunLoop 觸發 Source0 (非port) 回調。
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
/// 執行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
//若是沒有Sources0事件處理 而且 沒有超時,poll爲false
//若是有Sources0事件處理 或者 超時,poll都爲true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do..whil循環不會走該分支,由於didDispatchPortLastTime初始化是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//從緩衝區讀取消息
msg = (mach_msg_header_t *)msg_buffer;
/// 5. 若是有 Source1 (基於port) 處於 ready 狀態,直接處理這個 Source1 而後跳轉去處理消息。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//若是接收到了消息的話,前往第9步開始處理msg
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
/// 6.通知 Observers: RunLoop 的線程即將進入休眠(sleep)。
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//設置RunLoop爲休眠狀態
__CFRunLoopSetSleeping(rl);
複製代碼
msg = (mach_msg_header_t *)msg_buffer;
/// 7. 調用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。
/// • 一個基於 port 的Source 的事件。
/// • 一個 Timer 到時間了
/// • RunLoop 自身的超時時間到了
/// • 被其餘什麼調用者手動喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
複製代碼
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
複製代碼
總結:
主線程幾乎全部函數都從如下六個之一的函數調起:
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
用於向外部報告 RunLoop 當前狀態的更改,框架中不少機制都由 RunLoopObserver 觸發,如 CAAnimation
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
消息通知、非延遲的perform、非延遲的dispatch調用、block回調、KVO
block應用:
```
void (^block)(void) = ^{
NSLog(@"123");
};
block();
```
複製代碼
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"hello word"); });
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
延遲的perform, 延遲dispatch調用
[self performSelector:@selector(fire) withObject:nil afterDelay:1.0];
複製代碼
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
處理App內部事件、App本身負責管理(觸發),如UIEvent、CFSocket。普通函數調用,系統調用
複製代碼
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
由RunLoop和內核管理,Mach port驅動,如CFMachPort、CFMessagePort
複製代碼
runLoop
的超時時間就是使用 GCD
中的 dispatch_source_t
來實現的
執行GCD MainQueue
上的異步任務
runloop
用到了GCD
,當調用 dispatch_async(dispatch_get_main_queue(), block)
時,libDispatch
會向主線程的RunLoop
發送消息,RunLoop
會被喚醒,並從消息中取得這個 block
,並在回調 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE()
裏執行這個 block
。但這個邏輯僅限於 dispatch
到主線程,dispatch
到其餘線程仍然是由 libDispatch
處理的。
蘋果在主線程 RunLoop
裏註冊了兩個 ``Observer: 第一個Observer
監視的事件是 Entry
(即將進入Loop),其回調內會調用 _objc_autoreleasePoolPush()
建立自動釋放池。其 order 是-2147483647,優先級最高,保證建立釋放池發生在其餘全部回調以前。 第二個 Observer
監視了兩個事件: BeforeWaiting
(準備進入睡眠) 和 Exit
(即將退出Loop), BeforeWaiting
(準備進入睡眠)時調用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
釋放舊的池並建立新池; Exit
(即將退出Loop) 時調用 _objc_autoreleasePoolPop()
來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優先級最低,保證其釋放池子發生在其餘全部回調以後。
當在操做 UI
時,好比改變了 Frame
、更新了 UIView/CALayer
的層次時,或者手動調用了 UIView/CALayer
的 setNeedsLayout/setNeedsDisplay
方法後,這個 UIView/CALayer
就被標記爲待處理,並被提交到一個全局的容器去。 蘋果註冊了一個 Observer 監聽 BeforeWaiting
(即將進入休眠) 和Exit
(即將退出Loop) 事件,回調去執行。遍歷全部待處理的 UIView/CAlayer
以執行實際的繪製和調整,並更新 UI
界面。
蘋果註冊了一個 Source1
(基於 mach port 的) 用來接收系統事件,其回調函數爲 __IOHIDEventSystemClientQueueCallback()
。 當一個硬件事件(觸摸/鎖屏/搖晃等)發生後,首先由 IOKit.framework
生成一個 IOHIDEvent
事件並由 SpringBoard
接收。SpringBoard
只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,隨後用 mach port
轉發給須要的App
進程。隨後蘋果註冊的那個 Source1
就會觸發回調,並調用 _UIApplicationHandleEventQueue()
進行應用內部的分發。 _UIApplicationHandleEventQueue()
會把 IOHIDEvent
處理幷包裝成 UIEvent
進行處理或分發,其中包括識別 UIGesture/處理屏幕旋轉/發送給 UIWindow 等。一般事件好比 UIButton 點擊、touchesBegin/Move/End/Cancel 事件都是在這個回調中完成的。
當上面的 _UIApplicationHandleEventQueue() 識別了一個手勢時,其首先會調用 Cancel 將當前的 touchesBegin/Move/End 系列回調打斷。隨後系統將對應的 UIGestureRecognizer 標記爲待處理。蘋果註冊了一個 Observer 監測 BeforeWaiting (Loop即將進入休眠) 事件,這個Observer的回調函數是 _UIGestureRecognizerUpdateObserver(),其內部會獲取全部剛被標記爲待處理的 GestureRecognizer,並執行GestureRecognizer的回調。 當有 UIGestureRecognizer 的變化(建立/銷燬/狀態改變)時,這個回調都會進行相應處理。
NSTimer 其實就是 CFRunLoopTimerRef,他們之間是 toll-free bridged 的。一個 NSTimer 註冊到 RunLoop 後,RunLoop會爲其重複的時間點註冊好事件,RunLoop爲了節省資源,並不會在很是準確的時間點回調這個Timer。Timer 有個屬性叫作 Tolerance (寬容度),標示了當時間點到後,允許有多少最大偏差.
meInterval tolerance API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0));
複製代碼
NSTimer和performSEL方法其實是對CFRunloopTimerRef的封裝.
當調用 NSObject
的 performSelecter:afterDelay:
後,實際上其內部會建立一個 Timer
並添加到當前線程的RunLoop
中。因此若是當前線程沒有 RunLoop
,則這個方法會失效。 當調用 performSelector:onThread:
時,實際上其會建立一個 Timer
加到對應的線程去,一樣的,若是對應線程沒有 RunLoop 該方法也會失效。
爲了保證線程長期運轉,能夠在子線程中加入RunLoop
,而且給Runloop
設置item
,防止Runloop
自動退出。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
複製代碼
所謂的卡頓通常是在主線程作了耗時操做,卡頓監測的主要原理是在主線程的RunLoop
中添加一個 observer
,檢測從 即將處理Source(kCFRunLoopBeforeSources)
到 即將進入休眠 (kCFRunLoopBeforeWaiting)
花費的時間是否過長。若是花費的時間大於某一個闕值,則認爲卡頓,此時能夠輸出對應的堆棧調用信息。