iOS---RunLoop總結(jié)

Q: 如何實(shí)現(xiàn)常駐線程庭猩?


image.png

Q:什么是 RunLoop?
A:RunLoop是通過(guò)內(nèi)部維護(hù)的事件循環(huán)來(lái)對(duì)事件/消息進(jìn)行管理的一個(gè)對(duì)象
1). 沒(méi)有消息需要處理時(shí),休眠以避免資源占用


image.png

2). 有消息需要處理時(shí)全闷,立即被喚醒


image.png

應(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ù)為什么能保持不退出唐责?


image.png

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。

image.png

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:


image.png

RunLoop的mode

image.png

運(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:

image.png

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()


image.png

描述:在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核心

image.png

Q:滑動(dòng)TableView的時(shí)候唧垦,定時(shí)器還會(huì)生效嗎捅儒?


image.png

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.


企業(yè)微信截圖_bce4fdca-7c7d-4411-b576-b70e10e3a605.png
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

image.png
image.png

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)操作.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末审洞,一起剝皮案震驚了整個(gè)濱河市莱睁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芒澜,老刑警劉巖仰剿,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痴晦,居然都是意外死亡南吮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)阅酪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)旨袒,“玉大人汁针,你說(shuō)我怎么就攤上這事⊙饩。” “怎么了施无?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)必孤。 經(jīng)常有香客問(wèn)我猾骡,道長(zhǎng),這世上最難降的妖魔是什么敷搪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任兴想,我火速辦了婚禮,結(jié)果婚禮上赡勘,老公的妹妹穿的比我還像新娘嫂便。我一直安慰自己,他們只是感情好闸与,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布毙替。 她就那樣靜靜地躺著,像睡著了一般践樱。 火紅的嫁衣襯著肌膚如雪厂画。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,549評(píng)論 1 312
  • 那天拷邢,我揣著相機(jī)與錄音袱院,去河邊找鬼。 笑死瞭稼,一個(gè)胖子當(dāng)著我的面吹牛忽洛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弛姜,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼脐瑰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了廷臼?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绝页,失蹤者是張志新(化名)和其女友劉穎荠商,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體续誉,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莱没,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酷鸦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饰躲。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牙咏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嘹裂,到底是詐尸還是另有隱情妄壶,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布寄狼,位于F島的核電站丁寄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏泊愧。R本人自食惡果不足惜伊磺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望删咱。 院中可真熱鬧屑埋,春花似錦、人聲如沸痰滋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)即寡。三九已至徊哑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聪富,已是汗流浹背莺丑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墩蔓,地道東北人梢莽。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像奸披,于是被迫代替她去往敵國(guó)和親昏名。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 最近看了很多RunLoop的文章阵面,看完很懵逼轻局,決心整理一下,文章中大部分內(nèi)容都是引用大神們的样刷,但好歹對(duì)自己有個(gè)交代...
    小涼介閱讀 6,726評(píng)論 12 79
  • 一仑扑、RunLoop 的概念 字面意思是“消息循環(huán)、運(yùn)行循環(huán)”置鼻,RunLoop 實(shí)際上就是一個(gè)事件循環(huán)對(duì)象镇饮,這個(gè)對(duì)象...
    風(fēng)輕魚(yú)蛋閱讀 486評(píng)論 0 1
  • RunLoop是iOS開(kāi)發(fā)中非常底層的一個(gè)概念,我們來(lái)看看runloop的實(shí)現(xiàn)原理箕母,然后結(jié)合實(shí)例講解下runloo...
    西木柚子閱讀 4,120評(píng)論 8 60
  • 什么是RunLoop储藐?RunLoop的本質(zhì): Runloop-Demo[https://github.com/xi...
    二斤寂寞閱讀 739評(píng)論 2 0
  • 什么是runloop俱济? 從字面上理解,是一個(gè)運(yùn)行循環(huán)钙勃。一般情況下蛛碌,我們寫(xiě)一個(gè)函數(shù),從函數(shù)調(diào)用開(kāi)始到函數(shù)結(jié)束肺缕,執(zhí)行完...
    雨天多久就閱讀 955評(píng)論 0 0