什麼是RunLoop
從字面意思來看,就是執行迴圈的意思,其實就是在程式執行過程中迴圈做一些事情
RunLoop的應用範疇
下面幾個技術都需要在
RunLoop
下才能進行
定時器(Timer)、PerformSelector- GCD Async Main Queue- 事件響應、手勢識別、介面重新整理- 網路請求- AutoreleasePool
在main函式中,如果沒有
RunLoop
,那麼下面程式碼執行完第三行後就會退出程式
獲取RunLoop物件
iOS中有
2套API
來訪問和使用
RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop
是基於
CFRunLoopRef
的一層
OC包裝
,兩者都代表著
RunLoop物件
CFRunLoopRef
的開原始碼地址:https://opensource。apple。com/tarballs/CF/
在程式碼中的呼叫方法:
NSRunLoop *runloop = [NSRunLoop currentRunLoop];CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
RunLoop與執行緒
每條執行緒都有唯一的一個與之對應的
RunLoop物件
RunLoop
儲存在一個全域性的
Dictionary
裡,
執行緒
作為key,
RunLoop
作為value
執行緒剛建立時並沒有
RunLoop物件
,
RunLoop
會在第一次獲取它時建立
RunLoop
會線上程結束時銷燬
下面兩種方式都可以獲取到主執行緒的
RunLoop
,主執行緒的
RunLoop
會在
main函式
執行
UIApplicationMain
的時候自動建立,所以呼叫
[NSRunLoop currentRunLoop]
時已經有了
RunLoop物件
NSLog(@“%p %p”, [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);NSLog(@“%p %p”, CFRunLoopGetCurrent(), CFRunLoopGetMain());
在子執行緒裡一開始是沒有建立
RunLoop物件
的,需要呼叫
[NSRunLoop currentRunLoop]
才會建立
我們可以從原始碼
CFRunloop。c
檔案中檢視對應實現
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); // 如果沒有__CFRunLoops字典,先建立一個 if (!__CFRunLoops) { __CFUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); // 然後建立主執行緒的runloop,對應主執行緒的key儲存到字典中 CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // 從字典__CFRunLoops裡面獲取runloop // key: pthreadPointer CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); // 如果沒有與之對應的runloop物件,就建立一個新的runloop if (!loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); // 再次判斷是否有該runloop物件 loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 如果沒有,就將新建立的runloop賦值進去 if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don‘t release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFUnlock(&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;}
RunLoop的底層結構
我們透過原始碼可以看到
CFRunLoopRef
的本質是一個
__CFRunLoop
結構體型別
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;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; // 標記為通用的模式 CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; // 當前模式 CFMutableSetRef _modes; // 所有模式 struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFAbsoluteTime _runTime; CFAbsoluteTime _sleepTime; CFTypeRef _counterpart;};
再來檢視裡面的
_currentMode
型別為
CFRunLoopModeRef
,表示
RunLoop
的執行模式,本質是
__CFRunLoopMode
的結構體型別
typedef struct __CFRunLoopMode *CFRunLoopModeRef;struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; Boolean _stopped; char _padding[3]; // _sources0、_sources1就是平時需要處理的事情 CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; // 監聽器 CFMutableArrayRef _timers; // 定時器 CFMutableDictionaryRef _portToV1SourceMap; __CFPortSet _portSet; CFIndex _observerMask;#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#if DEPLOYMENT_TARGET_WINDOWS DWORD _msgQMask; void (*_msgPump)(void);#endif uint64_t _timerSoftDeadline; /* TSR */ uint64_t _timerHardDeadline; /* TSR */};
CFRunLoopRef
的內部結構可以用下圖來表述
CFRunLoopModeRef
在
RunLoop
中的關係可以用下圖表示
CFRunLoopModeRef
Mode的作用
一個
RunLoop
包含若干個
Mode
,每個
Mode
又包含若干個
Source0/Source1/Timer/Observer
-
RunLoop
啟動時只能選擇其中一個
Mode
,作為
currentMode
如果需要切換
Mode
,只能退出當前
Loop
,再重新選擇一個
Mode
進入 - 不同組的
Source0/Source1/Timer/Observer
能分隔開來,互不影響切換
Mode
程式不會退出,也是在內部做的切換
如果
Mode
裡沒有任何
Source0/Source1/Timer/Observer
,
RunLoop
會立馬退出
RunLoop裡面常見的Mode
kCFRunLoopDefaultMode(NSDefaultRunLoopMode)
:App的預設
Mode
,通常主執行緒是在這個
Mode
下執行
UITrackingRunLoopMode
:介面跟蹤
Mode
,用於
ScrollView
追蹤觸控滑動,保證介面滑動時不受其他
Mode
影響
kCFRunLoopCommonModes
預設包括
kCFRunLoopDefaultMode、UITrackingRunLoopMode
Mode中成員的用途
Source0 - 觸控事件處理 -
performSelector:onThread:
- Source1 - 基於
Port
的執行緒間通訊 - 系統事件捕捉(比如捕捉到點選事件,然後再交給
Source0
來做處理)- Timers -
NSTimer
定時器 -
performSelector:withObject:afterDelay:
- Observers - 用於監聽
RunLoop
的狀態(監聽
RunLoop
是否沒有事件要處理了) - UI重新整理(在
RunLoop
將要進入睡眠時喚醒重新整理UI) -
Autorelease pool
(在
RunLoop
將要進入睡眠時喚醒進行記憶體釋放)
RunLoop的執行邏輯
透過程式碼監聽
我們可以透過新增
觀察者Observer
的方式來監聽
RunLoop
的狀態以及模式變化
1。透過函式呼叫的方式來監聽
// 建立Observer// kCFRunLoopAllActivities:監聽所有狀態變化CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);// 新增Observer到RunLoop中CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);// 釋放(C語言中create建立後要對應釋放觀察者)CFRelease(observer);void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){ switch (activity) { case kCFRunLoopEntry: NSLog(@“kCFRunLoopEntry”); break; case kCFRunLoopBeforeTimers: NSLog(@“kCFRunLoopBeforeTimers”); break; case kCFRunLoopBeforeSources: NSLog(@“kCFRunLoopBeforeSources”); break; case kCFRunLoopBeforeWaiting: NSLog(@“kCFRunLoopBeforeWaiting”); break; case kCFRunLoopAfterWaiting: NSLog(@“kCFRunLoopAfterWaiting”); break; case kCFRunLoopExit: NSLog(@“kCFRunLoopExit”); break; default: break; }}
2。透過
block
回撥來監聽
// 建立ObserverCFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@“kCFRunLoopEntry - %@”, mode); CFRelease(mode); break; } case kCFRunLoopBeforeTimers: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@“kCFRunLoopBeforeTimers - %@”, mode); CFRelease(mode); break; } case kCFRunLoopBeforeSources: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@“kCFRunLoopBeforeSources - %@”, mode); CFRelease(mode); break; } case kCFRunLoopBeforeWaiting: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@“kCFRunLoopBeforeWaiting - %@”, mode); CFRelease(mode); break; } case kCFRunLoopAfterWaiting: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@“kCFRunLoopAfterWaiting - %@”, mode); CFRelease(mode); break; } case kCFRunLoopExit: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@“kCFRunLoopExit - %@”, mode); CFRelease(mode); break; } default: break; }});// 新增Observer到RunLoop中CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);// 釋放CFRelease(observer);
下面是
RunLoop
狀態變化的列舉註釋
透過原始碼分析
我們新建一個專案並執行,然後可以透過
LLDB的命令bt
來檢視詳細的呼叫過程
可以看到
RunLoop
的呼叫是從
CFRunLoopRunSpecific
函式開始的,那麼我們在原始碼可以找到該函式的實現來進行分析
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); 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; // 通知observer進入Loop if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 具體要做的事情 result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); if (currentMode->_observerMask & kCFRunLoopExit ) // 通知observer,退出Loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result;}
我們可以看到核心程式碼是在
__CFRunLoopRun
函式中
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); return kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; return kCFRunLoopRunStopped; } mach_port_name_t dispatchPort = MACH_PORT_NULL; Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ))); if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF(); #if USE_DISPATCH_SOURCE_FOR_TIMERS mach_port_name_t modeQueuePort = MACH_PORT_NULL; if (rlm->_queue) { modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue); if (!modeQueuePort) { CRASH(“Unable to get port for run loop mode queue (%d)”, -1); } }#endif dispatch_source_t timeout_timer = NULL; struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context)); if (seconds <= 0。0) { // instant timeout seconds = 0。0; timeout_context->termTSR = 0ULL; } else if (seconds <= TIMER_INTERVAL_LIMIT) { dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground(); timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_retain(timeout_timer); timeout_context->ds = timeout_timer; timeout_context->rl = (CFRunLoopRef)CFRetain(rl); timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds); dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout); dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel); uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL); dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL); dispatch_resume(timeout_timer); } else { // infinite timeout seconds = 9999999999。0; timeout_context->termTSR = UINT64_MAX; } Boolean didDispatchPortLastTime = true; int32_t retVal = 0; // —— 迴圈具體做的事情 do。。。while —— do {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED; voucher_t voucherCopy = NULL;#endif uint8_t msg_buffer[3 * 1024];#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI mach_msg_header_t *msg = NULL; mach_port_t livePort = MACH_PORT_NULL; // windows平臺#elif DEPLOYMENT_TARGET_WINDOWS HANDLE livePort = NULL; Boolean windowsMessageReceived = false;#endif __CFPortSet waitSet = rlm->_portSet; __CFRunLoopUnsetIgnoreWakeUps(rl); // 通知observer:即將處理timer if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 通知observer:即將處理sources if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 處理blocks __CFRunLoopDoBlocks(rl, rlm); // 處理source0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { // 處理blocks __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI msg = (mach_msg_header_t *)msg_buffer; // 判斷有無source1 if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { // 如果有就跳轉到handle_msg goto handle_msg; }#elif DEPLOYMENT_TARGET_WINDOWS if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) { goto handle_msg; }#endif } didDispatchPortLastTime = false; if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) // 通知observer:即將休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); // do not do any user callouts after this point (after notifying of sleeping) // Must push the local-to-this-activation ports in on every loop // iteration, as this mode could be run re-entrantly and we don’t // want these ports to get serviced。 __CFPortSetInsert(dispatchPort, waitSet); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); CFAbsoluteTime sleepStart = poll ? 0。0 : CFAbsoluteTimeGetCurrent();#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI#if USE_DISPATCH_SOURCE_FOR_TIMERS do { if (kCFUseCollectableAllocator) { // objc_clear_stack(0); //
細節分析
在
__CFRunLoopDoObservers
函數里會呼叫通知
observer
的程式碼來執行
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) { /* DOES CALLOUT */ CHECK_FOR_FORK(); CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0; if (cnt < 1) return; /* Fire the observers */ STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024) ? cnt : 1); CFRunLoopObserverRef *collectedObservers = (cnt <= 1024) ? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef)); CFIndex obs_cnt = 0; for (CFIndex idx = 0; idx < cnt; idx++) { CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx); if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) { collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo); } } __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); for (CFIndex idx = 0; idx < obs_cnt; idx++) { CFRunLoopObserverRef rlo = collectedObservers[idx]; __CFRunLoopObserverLock(rlo); if (__CFIsValid(rlo)) { Boolean doInvalidate = !__CFRunLoopObserverRepeats(rlo); __CFRunLoopObserverSetFiring(rlo); __CFRunLoopObserverUnlock(rlo); // 這句才是真正處理observer的程式碼 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context。info); if (doInvalidate) { CFRunLoopObserverInvalidate(rlo); } __CFRunLoopObserverUnsetFiring(rlo); } else { __CFRunLoopObserverUnlock(rlo); } CFRelease(rlo); } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); if (collectedObservers != buffer) free(collectedObservers);}
在
__CFRunLoopDoSources0
函式中會呼叫處理
Source0
的程式碼
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) { /* DOES CALLOUT */ CHECK_FOR_FORK(); CFTypeRef sources = NULL; Boolean sourceHandled = false; /* Fire the version 0 sources */ if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) { CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources); } if (NULL != sources) { __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); // sources is either a single (retained) CFRunLoopSourceRef or an array of (retained) CFRunLoopSourceRef if (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()) { CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources; __CFRunLoopSourceLock(rls); if (__CFRunLoopSourceIsSignaled(rls)) { __CFRunLoopSourceUnsetSignaled(rls); if (__CFIsValid(rls)) { __CFRunLoopSourceUnlock(rls); // 這句才是處理source0的真正的程式碼 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context。version0。perform, rls->_context。version0。info); CHECK_FOR_FORK(); sourceHandled = true; } else { __CFRunLoopSourceUnlock(rls); } } else { __CFRunLoopSourceUnlock(rls); } } else { CFIndex cnt = CFArrayGetCount((CFArrayRef)sources); CFArraySortValues((CFMutableArrayRef)sources, CFRangeMake(0, cnt), (__CFRunLoopSourceComparator), NULL); for (CFIndex idx = 0; idx < cnt; idx++) { CFRunLoopSourceRef rls = (CFRunLoopSourceRef)CFArrayGetValueAtIndex((CFArrayRef)sources, idx); __CFRunLoopSourceLock(rls); if (__CFRunLoopSourceIsSignaled(rls)) { __CFRunLoopSourceUnsetSignaled(rls); if (__CFIsValid(rls)) { __CFRunLoopSourceUnlock(rls); __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context。version0。perform, rls->_context。version0。info); CHECK_FOR_FORK(); sourceHandled = true; } else { __CFRunLoopSourceUnlock(rls); } } else { __CFRunLoopSourceUnlock(rls); } if (stopAfterHandle && sourceHandled) { break; } } } CFRelease(sources); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); } return sourceHandled;}
__CFRunLoopServiceMachPort
意味著
RunLoop
進入睡眠,也就是會阻塞當前執行緒,不再往後執行程式碼;只有當
RunLoop
被喚醒才會繼續執行後面的程式碼。這種阻塞是真正的阻塞,執行緒會進入休息,不再做任何事情,和寫個死迴圈的阻塞是不一樣的,死迴圈還是會執行程式碼,執行緒還是要一直工作的
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(); } // 核心層面的api呼叫 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;}
透過原始碼我們可以看到內部會呼叫核心層面的API來進行真正的休眠
呼叫API
我們可以分為使用者態和核心態的兩種狀態切換,
RunLoop
進入睡眠就是從使用者態切換到核心態的呼叫,然後有訊息喚醒執行緒時再切換到使用者態來處理事件
總結
RunLoop
的整個執行邏輯可以用下圖來表述
以
__CFRUNLOOP_IS_開頭的大寫函式
都是真正去實行呼叫的函式
RunLoop對GCD的特殊處理
我們執行下面程式碼,並在
GCD
的回撥裡打斷點,透過
LLDB的指令bt
列印來檢視
RunLoop
的執行過程
@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 處理一些子執行緒的邏輯 // 回到主執行緒去重新整理UI介面 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@“11111111111”); }); });}@end
可以看到
RunLoop
會處理
GCD
的回到主執行緒的呼叫情況,其他時候
GCD
都是有自己的邏輯去處理的,所以這是一個特殊情況
RunLoop的實際應用
解決NSTimer在滑動時停止工作的問題
如果定時器和滾動頁面同時存在的情況下,可能會遇到因為頁面滾動,而定時器停止的問題,見下面程式碼
// 透過這種方式新增定時器[NSTimer scheduledTimerWithTimeInterval:1。0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@“%d”, ++count);}];
原因是因為這樣新增定時器會預設指定在
RunLoop
的
NSDefaultRunLoopMode
下進行,而處理頁面滾動是在
RunLoop
的
UITrackingRunLoopMode
模式下進行的
解決辦法:透過以下方式新增定時器,並選擇
NSRunLoopCommonModes
模式
static int count = 0;NSTimer *timer = [NSTimer timerWithTimeInterval:1。0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@“%d”, ++count);}]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSRunLoopCommonModes
並不是真的模式,而是一種標記,會讓定時器同時能在這兩種模式下都進行工作
選擇通用模式會將這兩種模式都新增到
RunLoop
底層
__CFRunLoop
結構體裡的
_commonModes
成員變數裡,
_commonModes
意味著在通用模式下可以執行的模式列表,而
定時器timer
也會被加入到
_commonModeItems
列表中;沒有設定為通用模式的
定時器timer
只存在於
__CFRunLoopMode
裡面的成員變數
timers
這個列表中
控制執行緒生命週期(執行緒保活)
執行緒在程式執行中執行完程式碼就會自動銷燬了,見下面程式碼,可以利用
RunLoop
來進行執行緒保活
- (void)viewDidLoad { [super viewDidLoad]; Thread *thread = [[Thread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start];}- (void)run { NSLog(@“%s %@”, __func__, [NSThread currentThread]);}
完成實現程式碼如下
@interface Thread : NSThread@end@implementation Thread- (void)dealloc{ NSLog(@“%s”, __func__);}@end@interface ViewController ()@property (strong, nonatomic) Thread *thread;@property (assign, nonatomic, getter=isStoped) BOOL stopped;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self。stopped = NO; self。thread = [[Thread alloc] initWithBlock:^{ NSLog(@“%@——begin——”, [NSThread currentThread]); // 往RunLoop裡面新增Source\Timer\Observer [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf。isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@“%@——end——”, [NSThread currentThread]); }]; [self。thread start];}- (void)touchesBegan:(NSSet
細節分析
1。控制器增加一個
Thread *thread
的屬性來在多個方法能操作同一條執行緒
@property (strong, nonatomic) Thread *thread;
2。透過
block的方式
來建立執行緒是為了避免
@selector
裡面會對
Controller
進行引用,
Controller
又持有
thread
,會造成迴圈引用,無法釋放
self。thread = [[Thread alloc] initWithBlock:^{ }];[self。thread start];
3。在子執行緒裡建立一個
RunLoop物件
,如果沒有任何成員變數那麼建立完就會馬上退出,所以新增一個空白的
任務Source1
,也就是
[[NSPort alloc] init]
,讓
RunLoop
不會退出
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
新增的任務是個空的任務,
RunLoop
其實沒有任何事情要執行,所以會進入到睡眠狀態,所以會卡住執行緒不再往下執行程式碼
// 這句程式碼不會執行NSLog(@“%@——end——”, [NSThread currentThread]);
4。採用迴圈建立
RunLoop
而不使用
[[NSRunLoop currentRunLoop] run]
是因為
run方法
的底層就是死迴圈進行
runMode: beforeDate:
的呼叫,那麼就無法停止所有的
RunLoop
,所以我們自己實現
while迴圈
,並透過狀態值來控制什麼時候可以停止建立
RunLoop
while (weakSelf && !weakSelf。isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}
5。新增任務和結束
RunLoop
都要在同一個子執行緒來完成
[self performSelector:@selector(test) onThread:self。thread withObject:nil waitUntilDone:NO];// 子執行緒需要執行的任務- (void)test{ NSLog(@“%s %@”, __func__, [NSThread currentThread]);}[self performSelector:@selector(stopThread) onThread:self。thread withObject:nil waitUntilDone:YES];// 用於停止子執行緒的RunLoop- (void)stopThread{ // 設定標記為YES self。stopped = YES; // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@“%s %@”, __func__, [NSThread currentThread]); // 清空執行緒 self。thread = nil;}
6。
waitUntilDone:
表示是否執行完
selector的函式呼叫
再繼續往下執行程式碼,如果為NO,那麼執行完
performSelector
,控制器就真的銷燬了,這時再去呼叫到
self
就會報野指標的錯誤;設定為YES,程式碼會等子執行緒的
stopThread
執行完畢才會去真正銷燬控制器
7。
while迴圈
裡進行
self的判斷
是為了防止執行了
dealloc方法
會將弱指標已經至為
nil
了,那麼迴圈的判斷就會為
true
,再次進入建立
RunLoop
;所以要確保
self
有值的情況下判斷條件才成立
while (weakSelf && !weakSelf。isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}
[NSDate distantFuture]
表示的是未來無限長的時間
封裝最佳化
我們對上述程式碼進行封裝最佳化,以便更好的整合,如下所示
// PermenantThread。h typedef void (^PermenantThreadTask)(void);@interface PermenantThread : NSObject/** 開啟執行緒 *///- (void)run;/** 在當前子執行緒執行一個任務 */- (void)executeTask:(PermenantThreadTask)task;/** 結束執行緒 */- (void)stop;@end// PermenantThread。m/** Thread **/@interface Thread : NSThread@end@implementation Thread- (void)dealloc{ NSLog(@“%s”, __func__);}@end/** PermenantThread **/@interface PermenantThread()@property (strong, nonatomic) Thread *innerThread;@property (assign, nonatomic, getter=isStopped) BOOL stopped;@end@implementation PermenantThread#pragma mark - public methods- (instancetype)init{ if (self = [super init]) { self。stopped = NO; __weak typeof(self) weakSelf = self; self。innerThread = [[Thread alloc] initWithBlock:^{ [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf。isStopped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } }]; [self。innerThread start]; } return self;}//- (void)run//{// if (!self。innerThread) return;//// [self。innerThread start];//}- (void)executeTask:(PermenantThreadTask)task{ if (!self。innerThread || !task) return; [self performSelector:@selector(__executeTask:) onThread:self。innerThread withObject:task waitUntilDone:NO];}- (void)stop{ if (!self。innerThread) return; [self performSelector:@selector(__stop) onThread:self。innerThread withObject:nil waitUntilDone:YES];}- (void)dealloc{ NSLog(@“%s”, __func__); [self stop];}#pragma mark - private methods- (void)__stop{ self。stopped = YES; CFRunLoopStop(CFRunLoopGetCurrent()); self。innerThread = nil;}- (void)__executeTask:(PermenantThreadTask)task{ task();}@end
然後在
Controller
裡就可以簡單的整合使用了
@interface ViewController ()@property (strong, nonatomic) PermenantThread *thread;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; self。thread = [[PermenantThread alloc] init];}- (void)touchesBegan:(NSSet
也可以將
PermenantThread
的實現裡
RunLoop
的建立用
C語言的API
來實現
/** Thread **/@interface Thread : NSThread@end@implementation Thread- (void)dealloc{ NSLog(@“%s”, __func__);}@end/** PermenantThread **/@interface PermenantThread()@property (strong, nonatomic) Thread *innerThread;@end@implementation PermenantThread#pragma mark - public methods- (instancetype)init{ if (self = [super init]) { self。innerThread = [[Thread alloc] initWithBlock:^{ NSLog(@“begin——”); // 建立上下文(要初始化一下結構體) CFRunLoopSourceContext context = {0}; // 建立source CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); // 往Runloop中新增source CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); // 銷燬source CFRelease(source); // 啟動(第三個引數設定為false表示不退出,等同於加上while迴圈) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1。0e10, false); // while (weakSelf && !weakSelf。isStopped) {// // 第3個引數:returnAfterSourceHandled,設定為true,代表執行完source後就會退出當前loop// CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1。0e10, true);// } NSLog(@“end——”); }]; [self。innerThread start]; } return self;}//- (void)run//{// if (!self。innerThread) return;//// [self。innerThread start];//}- (void)executeTask:(PermenantThreadTask)task{ if (!self。innerThread || !task) return; [self performSelector:@selector(__executeTask:) onThread:self。innerThread withObject:task waitUntilDone:NO];}- (void)stop{ if (!self。innerThread) return; [self performSelector:@selector(__stop) onThread:self。innerThread withObject:nil waitUntilDone:YES];}- (void)dealloc{ NSLog(@“%s”, __func__); [self stop];}#pragma mark - private methods- (void)__stop{ CFRunLoopStop(CFRunLoopGetCurrent()); self。innerThread = nil;}- (void)__executeTask:(PermenantThreadTask)task{ task();}@end