Q: 如何實(shí)現(xiàn)常駐線程庭猩?
Q:什么是 RunLoop?
A:RunLoop是通過(guò)內(nèi)部維護(hù)的事件循環(huán)來(lái)對(duì)事件/消息進(jìn)行管理的一個(gè)對(duì)象
1). 沒(méi)有消息需要處理時(shí),休眠以避免資源占用
2). 有消息需要處理時(shí)全闷,立即被喚醒
應(yīng)用程序一般都是運(yùn)行在用戶態(tài)上的,用戶進(jìn)程包括平時(shí)開(kāi)發(fā)所使用的絕大多數(shù)的API都是針對(duì)于用戶層面的萍启。而當(dāng)我們發(fā)生系統(tǒng)調(diào)用总珠,需要用到一些關(guān)于操作系統(tǒng),底層相關(guān)的指令或者說(shuō)API勘纯,就會(huì)觸發(fā)系統(tǒng)調(diào)用局服。而有些系統(tǒng)調(diào)用就會(huì)發(fā)生狀態(tài)空間的切換,這種切換空間驳遵,或者說(shuō)區(qū)分用戶態(tài)和內(nèi)核態(tài)淫奔,實(shí)際上是對(duì)計(jì)算機(jī)的一些資源調(diào)度,包括資源管理的統(tǒng)一或者說(shuō)一致性的操作堤结,這樣就可以合理的安排資源調(diào)度唆迁,也可以避免特殊異常。
內(nèi)核態(tài)里的一些內(nèi)容可以對(duì)用戶態(tài)當(dāng)中的一些線程進(jìn)行調(diào)度竞穷。
Q:main函數(shù)為什么能保持不退出唐责?
A:在main函數(shù)中調(diào)用UIApplicationMain, 函數(shù)內(nèi)部會(huì)啟用運(yùn)行循環(huán)RunLoop,RunLoop是對(duì)事件循環(huán)的維護(hù)機(jī)制瘾带,做到有事做的時(shí)候做事鼠哥,沒(méi)事做的時(shí)候會(huì)通過(guò)用戶態(tài)到內(nèi)核態(tài)的切換,從而避免資源浪費(fèi),例如滑動(dòng)列表朴恳,處理網(wǎng)絡(luò)請(qǐng)求返回科盛,不斷接收消息,接收消息后菜皂,就會(huì)對(duì)消息進(jìn)行處理贞绵,處理完后就會(huì)繼續(xù)等待。這里的循環(huán)恍飘,是發(fā)生了用戶態(tài)到內(nèi)核態(tài)的切換榨崩,以及內(nèi)核態(tài)到用戶態(tài)的切換。
RunLoop相關(guān)問(wèn)題的關(guān)鍵章母,在于狀態(tài)的切換母蛛。
等待不等于死循環(huán)!H樵酢彩郊!
數(shù)據(jù)結(jié)構(gòu)
NSRunLoop是CFRunLoop的封裝,提供了面向?qū)ο蟮腁PI蚪缀。
NSRunLoop位于Foundation框架中秫逝,CFRunLoop位于Core Foundation當(dāng)中,蘋(píng)果對(duì)于CF開(kāi)頭的是開(kāi)源的询枚。
- NSRunLoop
- NSRunLoopMode
-
Source/Timer/Observer
image.png
pthread:對(duì)應(yīng)RunLoop與線程之間的關(guān)系
currentMode: CFRunLoopMode的數(shù)據(jù)結(jié)構(gòu)
modes: NSMutableSet<CFRunLoopMode *>
commonModes: NSMutableSet<NSString *> //特殊违帆,多注意
commonModeItems: 集合,里面包含多個(gè)Observer金蜀,Timer刷后, Source。
name: NSDefaultRunLoopMode
Sources0-Sources1: NSMutableSet
Observers: NSMutableArray
Timers: NSMutableArray
CFRunLoopSource
*source0: 需要手動(dòng)喚醒線程.
添加source0到對(duì)應(yīng)RunLoop中渊抄,不會(huì)主動(dòng)喚醒當(dāng)前線程尝胆。
*source1: 具備喚醒線程的能力。
CFRunLoopTimer
基于事件的定時(shí)器护桦,和NSTimer是toll-free bridged的含衔。
CFRunLoopObserver
觀測(cè)時(shí)間點(diǎn)
Q:可以監(jiān)聽(tīng)RunLoop哪些時(shí)間點(diǎn)
A:主要有6個(gè)。
1). KCFRunLoopEntry嘶炭,入口時(shí)機(jī)抱慌,當(dāng)RunLoop準(zhǔn)備啟動(dòng)的時(shí)候,系統(tǒng)會(huì)給出回調(diào)通知
2). KCFRunLoopBeforeTimes:通知觀察者眨猎,Timer將要對(duì)一些相關(guān)事件進(jìn)行處理了
3). KCFRunLoopBeforeSources:將要處理一些source事件
4). KCFRunLoopBeforeWaiting:通知當(dāng)前RunLoop即將進(jìn)入休眠狀態(tài)抑进, 非常重要的觀測(cè)點(diǎn),即將發(fā)生用戶態(tài)到內(nèi)核態(tài)的切換
5). KCFRunLoopAfterWaiting:內(nèi)核態(tài)切到用戶態(tài)不久的時(shí)間
6). KCFRunLoopExit:RunLoop退出的通知
線程和RunLoop之間是一一對(duì)應(yīng)的
Q:RunLoop和mode, 以及mode和其對(duì)應(yīng)的source, timer, observer是什么關(guān)系睡陪?
A:
RunLoop的mode
運(yùn)行在哪個(gè)mode上寺渗,就只能接收來(lái)自哪個(gè)mode的消息匿情。例如當(dāng)前運(yùn)行在Mode1上,此時(shí)Mode2當(dāng)中信殊,source, timer, observer發(fā)消息炬称,RunLoop是不會(huì)對(duì)事件進(jìn)行處理的。
Q:滑動(dòng)tableview的時(shí)候涡拘,如果tableview中有廣告欄玲躯,廣告欄已經(jīng)不會(huì)自動(dòng)滾動(dòng)了,是什么原因鳄乏?
A:
Q:比如timer既想在mode1上正常運(yùn)行跷车,在mode2也需要相應(yīng)的處理和事件回調(diào)接收,那么timer怎樣同時(shí)加入2個(gè)mode呢橱野?
A:系統(tǒng)是有提供添加到2個(gè)mode當(dāng)中的機(jī)制的朽缴。
commonMode
- commonMode不是實(shí)際存在的一種mode
- 是同步Source/Timer/Observer到多個(gè)Mode的一種技術(shù)方案
Q:有使用過(guò)commonMode嗎? 怎樣理解commonMode水援?
A:是同步Source/Timer/Observer到多個(gè)Mode的一種技術(shù)方案
事件循環(huán)實(shí)現(xiàn)機(jī)制
void CFRunLoopRun()
描述:在RunLoop啟動(dòng)后密强,會(huì)首先發(fā)送一個(gè)通知來(lái)告知觀察者,當(dāng)前RunLoop即將啟動(dòng)蜗元,RunLoop把將要處理的Timer/Source0事件發(fā)送或渤,如果有Source1要處理,使用goto語(yǔ)句许帐,代碼邏輯跳轉(zhuǎn)劳坑,處理喚醒時(shí)收到的消息。如果沒(méi)有Source1要處理成畦,線程將要休眠,同時(shí)發(fā)送通知給observer涝开。就要發(fā)生從用戶態(tài)到內(nèi)核態(tài)的切換循帐。喚醒RunLoop有幾種方式,比如timer事件回調(diào)舀武,外部手動(dòng)喚醒拄养。
Q:處于休眠方式的RunLoop, 可以通過(guò)哪些方式喚醒?
A:timer事件回調(diào)银舱,外部手動(dòng)喚醒瘪匿, Source1
RunLoop處于休眠狀態(tài),點(diǎn)擊屏幕寻馏,產(chǎn)生machPort, 基于machPort棋弥,最終會(huì)轉(zhuǎn)成Source1, 把主線程喚醒,運(yùn)行诚欠,處理顽染,把程序殺死時(shí)漾岳,就RunLoop退出,發(fā)送通知粉寞,即將退出RunLoop尼荆,RunLoop退出后,線程就銷毀掉了
RunLoop核心
Q:滑動(dòng)TableView的時(shí)候唧垦,定時(shí)器還會(huì)生效嗎捅儒?
A:TableView正常情況下是運(yùn)行在kCFRunLoopDefaultMode下,當(dāng)對(duì)TableView進(jìn)行滑動(dòng)時(shí)振亮,會(huì)發(fā)生mode的切換野芒,會(huì)切換到UITrackingRunLoopMode, 把timer, source, observer添加到某個(gè)mode, 如果當(dāng)前RunLoop運(yùn)行在另一個(gè)mode上,對(duì)應(yīng)的timer, source, observer是無(wú)法進(jìn)行后續(xù)的處理和回調(diào)双炕。
***Mode和Mode之前是隔離開(kāi)的狞悲。
Q:如果想要滑動(dòng)TableView的時(shí)候,定時(shí)器繼續(xù)生效妇斤,要怎么做摇锋?
A:
commonMode不是實(shí)際的mode, 它只是為其他的mode打上common的標(biāo)記,可以把某個(gè)事情源同步到多個(gè)mode.
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
//首先會(huì)提取RunLoop的commonModes
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//同時(shí)判斷runloop對(duì)應(yīng)的commonItems是否為空,若為空,會(huì)重新創(chuàng)建集合
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
// commonModelItems 增加該 timer
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) {
//再把timer和Runloop封裝成一個(gè)context,
CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
// 遍歷每一個(gè) commonMode站超,添加該 timer.
//之后對(duì)集合中的每一個(gè)元素,都調(diào)用__CFRunLoopAddItemToCommonModes函數(shù)(對(duì)象元素,context)
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
if (NULL != rlm) {
if (NULL == rlm->_timers) {
CFArrayCallBacks cb = kCFTypeArrayCallBacks;
cb.equal = NULL;
rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
}
}
if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
__CFRunLoopTimerLock(rlt);
if (NULL == rlt->_runLoop) {
rlt->_runLoop = rl;
} else if (rl != rlt->_runLoop) {
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
return;
}
CFSetAddValue(rlt->_rlModes, rlm->_name);
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
// 刪除 timers 中的該 timer荸恕,并新增該 timer
__CFRepositionTimerInMode(rlm, rlt, false);
__CFRunLoopTimerFireTSRUnlock();
if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
// Normally we don't do this on behalf of clients, but for
// backwards compatibility due to the change in timer handling...
if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
}
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
//取當(dāng)前mode的名字,然后吧Runloop和Item取出來(lái)
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
//根據(jù)當(dāng)前item類型判斷,來(lái)決定調(diào)用CFRunLoopAddSource還是CFRunLoopAddObserver還是CFRunLoopAddTimer,并不是循環(huán)調(diào)用,因?yàn)閭鬟M(jìn)來(lái)的modeName 參數(shù)已經(jīng)從commonMode變成被打上了標(biāo)記的具體實(shí)際的mode
if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
}
}
RunLoop與多線程之間的關(guān)系
Q:線程與RunLoop是什么關(guān)系?
A:線程和RunLoop是一一對(duì)應(yīng)的死相。自己創(chuàng)建的線程默認(rèn)是沒(méi)RunLoop的融求,需要手動(dòng)創(chuàng)建RunLoop
Q:怎樣實(shí)現(xiàn)常駐線程?
A:
1). 為當(dāng)前線程開(kāi)啟RunLoop
2). 向該RunLoop中添加一個(gè)Port/Source等維持RunLoop的事件循環(huán)
3). 啟動(dòng)該RunLoop
Q:怎樣保證子線程數(shù)據(jù)回來(lái)更新UI的時(shí)候不打斷用戶的滑動(dòng)操作算撮?
A:用戶滑動(dòng)操作生宛, RunLoop是運(yùn)行在UITrackingRunLoopMode下甩挫,而一般網(wǎng)絡(luò)請(qǐng)求是放在子線程下祟敛,而子線程返回給主線程的數(shù)據(jù),用來(lái)更新UI疟游,可以把子線程返回給主線程的數(shù)據(jù)包裝起來(lái), 然后提交到主線程的default模式下, 那么拋回來(lái)的任務(wù), 就不會(huì)執(zhí)行,當(dāng)我們手停止滑動(dòng)操作后,當(dāng)前線程切換到default模式下,這時(shí)會(huì)處理子線程返回給主線程的任務(wù),這時(shí)就不會(huì)打斷用戶的滑動(dòng)操作.