iOS RunLoop

什麼是RunLoop

從字面意思來看,就是執行迴圈的意思,其實就是在程式執行過程中迴圈做一些事情

RunLoop的應用範疇

下面幾個技術都需要在

RunLoop

下才能進行

定時器(Timer)、PerformSelector- GCD Async Main Queue- 事件響應、手勢識別、介面重新整理- 網路請求- AutoreleasePool

在main函式中,如果沒有

RunLoop

,那麼下面程式碼執行完第三行後就會退出程式

iOS 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

的內部結構可以用下圖來表述

iOS RunLoop

CFRunLoopModeRef

RunLoop

中的關係可以用下圖表示

iOS 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

狀態變化的列舉註釋

iOS RunLoop

透過原始碼分析

我們新建一個專案並執行,然後可以透過

LLDB的命令bt

來檢視詳細的呼叫過程

iOS RunLoop

可以看到

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); // memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; // 等待別的訊息來喚醒當前執行緒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { // Drain the internal queue。 If one of the callout blocks sets the timerFired flag, break out and service the timer。 while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue)); if (rlm->_timerFired) { // Leave livePort as the queue port, and service timers below rlm->_timerFired = false; break; } else { if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); } } else { // Go ahead and leave the inner loop。 break; } } while (1);#else if (kCFUseCollectableAllocator) { // objc_clear_stack(0); // memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);#endif #elif DEPLOYMENT_TARGET_WINDOWS // Here, use the app-supplied message queue mask。 They will set this if they are interested in having this run loop receive windows messages。 __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);#endif __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); rl->_sleepTime += (poll ? 0。0 : (CFAbsoluteTimeGetCurrent() - sleepStart)); // Must remove 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。 Also, we don’t want them left // in there if this function returns。 __CFPortSetRemove(dispatchPort, waitSet); __CFRunLoopSetIgnoreWakeUps(rl); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) // 通知observer:結束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); // 呼叫handle_msg handle_msg:; __CFRunLoopSetIgnoreWakeUps(rl);#if DEPLOYMENT_TARGET_WINDOWS if (windowsMessageReceived) { // These Win32 APIs cause a callout, so make sure we‘re unlocked first and relocked after __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); if (rlm->_msgPump) { rlm->_msgPump(); } else { MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) { TranslateMessage(&msg); DispatchMessage(&msg); } } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced // Use 0 for the mask so windows messages are ignored this time。 Also use 0 for the timeout, because we’re just checking to see if the things are signalled right now —— we will wait on them again later。 // NOTE: Ignore the dispatch source (it‘s not in the wait set anymore) and also don’t run the observers here since we are polling。 __CFRunLoopSetSleeping(rl); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); __CFRunLoopUnsetSleeping(rl); // If we have a new live port then it will be handled below as normal } #endif // 判斷是怎麼醒來的 if (MACH_PORT_NULL == livePort) { CFRUNLOOP_WAKEUP_FOR_NOTHING(); // handle nothing } else if (livePort == rl->_wakeUpPort) { CFRUNLOOP_WAKEUP_FOR_WAKEUP(); // do nothing on Mac OS#if DEPLOYMENT_TARGET_WINDOWS // Always reset the wake up port, or risk spinning forever ResetEvent(rl->_wakeUpPort);#endif } #if USE_DISPATCH_SOURCE_FOR_TIMERS // 被timer喚醒 else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer, because we apparently fired early // 處理timers __CFArmNextTimerInMode(rlm, rl); } }#endif#if USE_MK_TIMER_TOO // 被timer喚醒 else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set。 For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early。 The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be ‘too early’ for the next timer, and no timers are handled。 // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e。g。 adding or removing timers)。 The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself。 9308754 if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer // 處理timers __CFArmNextTimerInMode(rlm, rl); } }#endif // 被gcd喚醒 else if (livePort == dispatchPort) { CFRUNLOOP_WAKEUP_FOR_DISPATCH(); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);#if DEPLOYMENT_TARGET_WINDOWS void *msg = 0;#endif // 處理gcd相關事情 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; didDispatchPortLastTime = true; } else { // 被source1喚醒 CFRUNLOOP_WAKEUP_FOR_SOURCE(); // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD。 CFMachPortBoost will look in the TSD for the voucher。 By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again。 voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release); // Despite the name, this works for windows handles as well CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); if (rls) {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI // 處理source1 mach_msg_header_t *reply = NULL; sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; if (NULL != reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply); }#elif DEPLOYMENT_TARGET_WINDOWS sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;#endif } // Restore the previous voucher _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release); } #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);#endif // 處理block __CFRunLoopDoBlocks(rl, rlm); // 根據retVal返回值來決定要做什麼 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; } #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_revert(voucherState); os_release(voucherCopy);#endif } while (0 == retVal); // —— 迴圈結束 ——- if (timeout_timer) { dispatch_source_cancel(timeout_timer); dispatch_release(timeout_timer); } else { free(timeout_context); } // 如果不等於0就退出loop return retVal;}

細節分析

__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

進入睡眠就是從使用者態切換到核心態的呼叫,然後有訊息喚醒執行緒時再切換到使用者態來處理事件

iOS RunLoop

總結

RunLoop

的整個執行邏輯可以用下圖來表述

iOS 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

都是有自己的邏輯去處理的,所以這是一個特殊情況

iOS RunLoop

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 *)touches withEvent:(UIEvent *)event{ if (!self。thread) return; [self performSelector:@selector(test) onThread:self。thread withObject:nil waitUntilDone:NO];}// 子執行緒需要執行的任務- (void)test{ NSLog(@“%s %@”, __func__, [NSThread currentThread]);}- (IBAction)stop { if (!self。thread) return; // 在子執行緒呼叫stop(waitUntilDone設定為YES,代表子執行緒的程式碼執行完畢後,這個方法才會往下走) [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;}- (void)dealloc{ NSLog(@“%s”, __func__); [self stop];}@end

細節分析

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 *)touches withEvent:(UIEvent *)event{ [self。thread executeTask:^{ NSLog(@“執行任務 - %@”, [NSThread currentThread]); }];}- (IBAction)stop { [self。thread stop];}- (void)dealloc{ NSLog(@“%s”, __func__);}@end

也可以將

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