1. Run Loops
Run loops是線程基礎(chǔ)機(jī)構(gòu)中非常重要的一環(huán)缤削。它是一個處理事件的循環(huán)窘哈,幫助你安排事件工作和協(xié)調(diào)接收到的事件。其目的是在工作時讓線程處理亭敢,在沒工作時讓線程休眠滚婉。
runloop的不是完全自管理的,你必須在適當(dāng)?shù)臅r機(jī)啟動它來處理接收到的事件帅刀。你可以使用Cocoa和Core Foundation中的run loop object
來配置和管理線程的runloop让腹。你不必手動創(chuàng)建一個runloop远剩,因?yàn)榘ㄖ骶€程在內(nèi)的每個線程都包含一個run loop object
。但是你得手動啟動子線程的runloop骇窍,而主線程的runloop在引用啟動時就被啟動運(yùn)行了瓜晤。
2. Run Loop解析
runloop的內(nèi)部是一個do-while循環(huán)。它接收兩種不同類型的sources
(Input sources
和Timer sources
)分發(fā)的事件腹纳。Input sources
分發(fā)異步的事件痢掠,Timer sources
分發(fā)同步的事件。
此外嘲恍,在處理sources分發(fā)的事件時足画,runloop同時會產(chǎn)生關(guān)于runloop狀態(tài)的通知。你可以注冊runloop observers來接收這些通知佃牛。
2.1Run Loop Modes
runloop mode是一個input sources锌云、timers和observers的集合。每次你運(yùn)行一個runloop時吁脱,你必須指定一個mode。同時只有在這個mode中的input sources彬向、timers和observers才能得到處理兼贡。
每個mode都可以指定一個name用來區(qū)分。
Mode | Name | Description |
---|---|---|
Default | NSDefaultRunLoopMode kCFRunLoopDefaultMode | 默認(rèn)mode娃胆,runloop中最常用的mode |
Connection | NSConnectionReplyMode | 用于監(jiān)測NSConnection對象的回應(yīng)遍希,很少需要用到 |
Modal | NSModalPanelRunLoopMode | 用于識別事件的modal |
Event tracking | NSEventTrackingRunLoopMode | 追蹤觸摸手勢,限制其他接收到的事件里烦,確保界面刷新不會卡頓凿蒜。 |
Common modes | NSRunLoopCommonModes kCFRunLoopCommonModes | 常用的modes組合。默認(rèn)包含default胁黑、modal和event tracking modes废封。 |
2.2 Input Sources
input sources包含port-based sources和custom input sources。port-based sources監(jiān)聽?wèi)?yīng)用的Mach ports丧蘸。custom input sources監(jiān)聽事件的custom sources漂洋。
2.2.1 Port-Based Sources(source1)
在Cocoa中,你只需要使用NSPort
來增加port到runloop中力喷。這個port對象會為你處理需要的input sources的創(chuàng)建和配置刽漂。在Core Foundation中,你必須通過CFMachPortRef
,CFMessagePortRef
,CFSocketRef
手動創(chuàng)建port和它的runloop source弟孟。
2.2.2 Custom Input Sources(source0)
你需要使用CFRunLoopSourceRef
相關(guān)的函數(shù)來創(chuàng)建一個自定義輸入源贝咙。你要通過回調(diào)函數(shù)來配置自定義輸入源,從而處理接收到的事件和在從runloop中移除時關(guān)閉source拂募。同時你必須定義事件的分發(fā)機(jī)制庭猩。相關(guān)的例子可以參考 Defining a Custom Input Source窟她。它主要處理UIEvent、CFSocket這樣的事件眯娱。
2.2.3 Cocoa Perform Selector Sources
Cocoa定義了一種自定義輸入源礁苗,它允許你在任何線程上perform selector。與port-based sources類似徙缴,在目標(biāo)線程上perform selector請求也是連續(xù)的试伙,這樣減輕了線程中多方法執(zhí)行的同步問題。與port-based sources不同的是于样,perform selector在執(zhí)行后自動從runloop中移除疏叨。
當(dāng)然,perform selector要想在指定線程中執(zhí)行穿剖,這個線程的runloop必須是啟動的蚤蔓。
2.3 Timer Sources
計時器用來通知線程做指定的一些事情。但是計時器的時間不是實(shí)時的糊余,有時可能會延后秀又。當(dāng)這個線程的runloop不在計時器指定的mode中時,那么計時器將會被暫停贬芥,直到runloop重新回到計時器指定的mode中去吐辙。
2.4 Run Loop Observers
一個計時器或者輸入源開始時會給觀察者發(fā)送通知。runloop中的通知包含以下:
- 進(jìn)入runloop
- runloop即將處理計時器
- runloop即將處理輸入源
- runloop即將進(jìn)入睡眠
- runloop被喚醒
- runloop退出
2.5 run loop事件隊(duì)列
3 什么時候使用Run Loop
只有當(dāng)你創(chuàng)建一個子線程時你才需要啟動runloop蘸劈。對于子線程昏苏,你需要考慮是否需要啟動runloop。比如威沫,你需要執(zhí)行一些耗時的操作時贤惯,就不需要啟動runloop。啟動runloop是為了能夠與線程互動棒掠。你需要啟動runloop在以下情況:
- 使用輸入源來與其他線程通信
- 在子線程上使用計時器
- 使用
performselector...
方法 - 需要子線程執(zhí)行周期性任務(wù)
4.Run Loop實(shí)踐
4.1 TableView中實(shí)現(xiàn)平滑滾動延遲加載圖片
由于滾動時runloop的current mode是UITrackingRunLoopMode孵构。如果此時圖片在加載且較慢的話,會影響runloop的循環(huán)句柠,導(dǎo)致滾動卡頓浦译。因此可以利用CFRunLoopMode特性,將圖片的加載放到NSDefaultRunLoopMode的mode里溯职,這樣在滾動的時候就不會加載圖片精盅,等滾動完current mode切換為NSDefaultRunLoopMode時再加載圖片。
UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
withObject:downloadedImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
4.2 卡頓檢測
通過分析上面講過的runloop事件隊(duì)列谜酒。runloop處理事件主要集中在step3(kCFRunLoopBeforeSources)之后以及step9(kCFRunLoopAfterWaiting)之后叹俏。所以可以創(chuàng)建一個observer來觀察這兩個狀態(tài),同時通過semphore來監(jiān)測這兩個狀態(tài)的處理時間僻族。如果好幾次出現(xiàn)大于某個值則認(rèn)為是出現(xiàn)了UI卡頓粘驰。
- (void)startMonitor {
if (_runLoopObserver) {
return;
}
self.semaphore = dispatch_semaphore_create(0);
CFRunLoopObserverContext context = {
0,
(__bridge void*)self,
NULL,
NULL
};
_runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
CFRunLoopAddObserver(CFRunLoopGetMain(), _runLoopObserver, kCFRunLoopCommonModes);
dispatch_async(fluency_monitor_queue(), ^{
while (YES) {
long st = dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC));
if (st != 0) {
if (!_runLoopObserver) {
_timeoutCount = 0;
self.semaphore = 0;
self.runLoopActivity = 0;
return;
}
if (self.runLoopActivity == kCFRunLoopBeforeSources || self.runLoopActivity == kCFRunLoopAfterWaiting) {
if (++_timeoutCount < 3) {
continue;
}
//出現(xiàn)了UI卡頓屡谐。可以打印堆棧信息看看哪個線程出了問題蝌数。
}
}
}
});
}
static void runLoopObserverCallBack(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
StackMonitor *monitor = (__bridge StackMonitor*)info;
monitor.runLoopActivity = activity;
dispatch_semaphore_signal(monitor.semaphore);
}
5. Run Loop源碼
CFRunLoopRef的代碼是開源的愕掏,你可以在http://opensource.apple.com/tarballs/CF/里面下載到包含CFRunLoopRef在內(nèi)的整個CoreFoundation源碼。
CFRunLoop結(jié)構(gòu)體如下顶伞,包含了對應(yīng)的線程饵撑,modes等。
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; //runloop對應(yīng)的線程
uint32_t _winthread;
CFMutableSetRef _commonModes; //所有標(biāo)記為common的mode集合
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; //當(dāng)前運(yùn)行的mode
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
RunLoopMode包含了名稱和source0唆貌、source1滑潘、observers、timers的集成等锨咙。RunLoop管理RunLoopMode语卤,而RunLoopMode管理具體的事件。
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //mode名稱
Boolean _stopped; //mode是否被終止
char _padding[3];
CFMutableSetRef _sources0; //source0(custom input source)
CFMutableSetRef _sources1; //source1(port-based input source)
CFMutableArrayRef _observers; //通知者
CFMutableArrayRef _timers; //計時器
CFMutableDictionaryRef _portToV1SourceMap; //記錄source1port的字典
__CFPortSet _portSet;
CFIndex _observerMask; //觀察的runloop的狀態(tài)
#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 */
};
CFRunLoopSource結(jié)構(gòu)體如下:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops; //添加該source的runloop
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
CFRunLoopObserver酪刀,觀察runloop的狀態(tài)粹舵,并拋出回調(diào)。
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //觀察的runloop
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */ //狀態(tài)
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
CFRunLoopActivity的6種狀態(tài)骂倘。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入run loop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理timer
kCFRunLoopBeforeSources = (1UL << 2),//即將處理source
kCFRunLoopBeforeWaiting = (1UL << 5),//即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//被喚醒但是還沒開始處理事件
kCFRunLoopExit = (1UL << 7),//run loop已經(jīng)退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
CFRunLoopTimer可以在設(shè)定的時間點(diǎn)拋出回調(diào)齐婴,另外它包含了mode集合,因此可以被添加到任何mode中去稠茂。
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //添加該timer的runloop
CFMutableSetRef _rlModes; //mode集合
CFAbsoluteTime _nextFireDate; //下一次計時器開始時間
CFTimeInterval _interval; /* immutable */ //理想時間間隔
CFTimeInterval _tolerance; /* mutable */ //時間偏差
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
在Core Foundation中通過以下兩個API來啟動runloop。第一個使用default mode情妖,第二個使用指定的mode睬关。
void CFRunLoopRun(void)
SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
//do-while循環(huán)
do {
//DefaultMode方式啟動CFRunLoopRunSpecific
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
通過源碼可知,內(nèi)部都是運(yùn)行了CFRunLoopRunSpecific
函數(shù)毡证。我們來看一下實(shí)現(xiàn)电爹。
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
//1.如果已經(jīng)釋放掉r1,返回kCFRunLoopRunFinished
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
//2.對r1上鎖
__CFRunLoopLock(rl);
//3.根據(jù)modeName找到當(dāng)前的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//4.如果沒有找到或者mode為空,退出runloop
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);
//5.取出先前的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//6.設(shè)置當(dāng)前的mode
rl->_currentMode = currentMode;
//7.初始化一個kCFRunLoopRunFinished的result
int32_t result = kCFRunLoopRunFinished;
//8.如果當(dāng)前_observerMask為kCFRunLoopEntry,通知observer,然后進(jìn)入__CFRunLoopRun
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//9.如果當(dāng)前_observerMask為kCFRunLoopExit料睛,通知observer丐箩,退出
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
由上面的代碼可知,mode必須存在恤煞,且包含modeItem屎勘,這個runloop才能運(yùn)行。在進(jìn)入和退出runloop之前通知observer居扒。另外概漱,runloop的核心函數(shù)是__CFRunLoopRun
。我們來看一下它的邏輯喜喂。
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
........
//2.通知observer瓤摧,即將觸發(fā)計時器回調(diào)
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3.通知observer竿裂,即將觸發(fā)source0回調(diào)
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//執(zhí)行加入當(dāng)前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
//4.處理source0事件
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//執(zhí)行加入當(dāng)前runloop的block
__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;
//5.接收dispatchPort端口的source1事件,如果接收到照弥,前往第9步處理msg
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
//6.通知observer腻异,runloop即將休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//runloop進(jìn)入休眠
__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
//7.do-while循環(huán),用于接收等待端口的消息这揣。
// ? 一個內(nèi)核端口的source事件
// ? 一個計時器到時間了
// ? RunLoop自身的超時時間到了
// ? 被其他什么調(diào)用者手動喚醒
//滿足以上四個條件中一個悔常,退出循環(huán)
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
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);
// <rdar://problem/16393959>
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);
//8.通知observer,runloop被喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//9.處理接收到的事件
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
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
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
//9.1如果計時器時間到了曾沈,觸發(fā)回調(diào)
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
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
//9.2如果是dispatch到main_queue的block这嚣,執(zhí)行block
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
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
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) {
//9.3如果有source1事件,處理source1事件
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
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
//執(zhí)行加入到loop中的block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) { //runloop超時
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) { //runloop被stop
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) { //mode被stop
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { //mode為空
retVal = kCFRunLoopRunFinished;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
//如果沒超時塞俱,mode沒空姐帚,loop沒被停止,繼續(xù)loop
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
6. 總結(jié)
本文主要分析了runloop的機(jī)制以及源碼障涯,并給出了幾種相應(yīng)的應(yīng)用場景罐旗。
7. 參考
Run Loops
深入理解RunLoop
RunLoop系列之源碼分析
iOS開發(fā)優(yōu)化篇之卡頓檢測
CFRunLoop