iOS面試題:RunLoop剖析

一遵倦、RunLoop概念

RunLoop是通過內(nèi)部維護(hù)的事件循環(huán)(Event Loop)來對事件/消息進(jìn)行管理的一個對象署鸡。

1、沒有消息處理時地啰,休眠已避免資源占用,由用戶態(tài)切換到內(nèi)核態(tài)(CPU-內(nèi)核態(tài)和用戶態(tài))
2讲逛、有消息需要處理時亏吝,立刻被喚醒,由內(nèi)核態(tài)切換到用戶態(tài)

為什么main函數(shù)不會退出盏混?

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

UIApplicationMain內(nèi)部默認(rèn)開啟了主線程的RunLoop蔚鸥,并執(zhí)行了一段無限循環(huán)的代碼(不是簡單的for循環(huán)或while循環(huán))

//無限循環(huán)代碼模式(偽代碼)
int main(int argc, char * argv[]) {        
    BOOL running = YES;
    do {
        // 執(zhí)行各種任務(wù),處理各種事件
        // ......
    } while (running);

    return 0;
}

UIApplicationMain函數(shù)一直沒有返回许赃,而是不斷地接收處理消息以及等待休眠株茶,所以運(yùn)行程序之后會保持持續(xù)運(yùn)行狀態(tài)。

二图焰、RunLoop的數(shù)據(jù)結(jié)構(gòu)

NSRunLoop(Foundation)CFRunLoop(CoreFoundation)的封裝启盛,提供了面向?qū)ο蟮腁PI
RunLoop 相關(guān)的主要涉及五個類:

CFRunLoop:RunLoop對象
CFRunLoopMode:運(yùn)行模式
CFRunLoopSource:輸入源/事件源
CFRunLoopTimer:定時源
CFRunLoopObserver:觀察者

1、CFRunLoop

pthread(線程對象技羔,說明RunLoop和線程是一一對應(yīng)的)僵闯、currentMode(當(dāng)前所處的運(yùn)行模式)、modes(多個運(yùn)行模式的集合)藤滥、commonModes(模式名稱字符串集合)鳖粟、commonModelItems(Observer,Timer,Source集合)構(gòu)成

2、CFRunLoopMode

由name拙绊、source0向图、source1、observers标沪、timers構(gòu)成

3榄攀、CFRunLoopSource

分為source0和source1兩種

  • source0:
    即非基于port的,也就是用戶觸發(fā)的事件金句。需要手動喚醒線程檩赢,將當(dāng)前線程從內(nèi)核態(tài)切換到用戶態(tài)
  • source1:
    基于port的,包含一個 mach_port 和一個回調(diào)违寞,可監(jiān)聽系統(tǒng)端口和通過內(nèi)核和其他線程發(fā)送的消息贞瞒,能主動喚醒RunLoop偶房,接收分發(fā)系統(tǒng)事件。
    具備喚醒線程的能力

4军浆、CFRunLoopTimer

基于時間的觸發(fā)器棕洋,基本上說的就是NSTimer。在預(yù)設(shè)的時間點(diǎn)喚醒RunLoop執(zhí)行回調(diào)乒融。因?yàn)樗腔赗unLoop的掰盘,因此它不是實(shí)時的(就是NSTimer 是不準(zhǔn)確的。 因?yàn)镽unLoop只負(fù)責(zé)分發(fā)源的消息簇抵。如果線程當(dāng)前正在處理繁重的任務(wù)庆杜,就有可能導(dǎo)致Timer本次延時射众,或者少執(zhí)行一次)碟摆。

5、CFRunLoopObserver

監(jiān)聽以下時間點(diǎn):CFRunLoopActivity

  • kCFRunLoopEntry
    RunLoop準(zhǔn)備啟動
  • kCFRunLoopBeforeTimers
    RunLoop將要處理一些Timer相關(guān)事件
  • kCFRunLoopBeforeSources
    RunLoop將要處理一些Source事件
  • kCFRunLoopBeforeWaiting
    RunLoop將要進(jìn)行休眠狀態(tài),即將由用戶態(tài)切換到內(nèi)核態(tài)
  • kCFRunLoopAfterWaiting
    RunLoop被喚醒叨橱,即從內(nèi)核態(tài)切換到用戶態(tài)后
  • kCFRunLoopExit
    RunLoop退出
  • kCFRunLoopAllActivities
    監(jiān)聽所有狀態(tài)

6典蜕、各數(shù)據(jù)結(jié)構(gòu)之間的聯(lián)系

線程和RunLoop一一對應(yīng), RunLoop和Mode是一對多的罗洗,Mode和source愉舔、timer、observer也是一對多的

image

三伙菜、RunLoop的Mode

關(guān)于Mode首先要知道一個RunLoop 對象中可能包含多個Mode轩缤,且每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode(CurrentMode)贩绕。切換 Mode火的,需要重新指定一個 Mode 。主要是為了分隔開不同的 Source淑倾、Timer馏鹤、Observer,讓它們之間互不影響娇哆。

image

當(dāng)RunLoop運(yùn)行在Mode1上時湃累,是無法接受處理Mode2或Mode3上的Source、Timer碍讨、Observer事件的

總共是有五種CFRunLoopMode:

  • kCFRunLoopDefaultMode:默認(rèn)模式治力,主線程是在這個運(yùn)行模式下運(yùn)行

  • UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他Mode影響)

  • UIInitializationRunLoopMode:在剛啟動App時第進(jìn)入的第一個 Mode勃黍,啟動完成后就不再使用

  • GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件琴许,通常用不到

  • kCFRunLoopCommonModes:偽模式,不是一種真正的運(yùn)行模式溉躲,是同步Source/Timer/Observer到多個Mode中的一種解決方案

四榜田、RunLoop的實(shí)現(xiàn)機(jī)制

image

這張圖在網(wǎng)上流傳比較廣益兄。
對于RunLoop而言最核心的事情就是保證線程在沒有消息的時候休眠,在有消息時喚醒箭券,以提高程序性能净捅。RunLoop這個機(jī)制是依靠系統(tǒng)內(nèi)核來完成的(蘋果操作系統(tǒng)核心組件Darwin中的Mach)。

image

RunLoop通過mach_msg()函數(shù)接收辩块、發(fā)送消息蛔六。它的本質(zhì)是調(diào)用函數(shù)mach_msg_trap(),相當(dāng)于是一個系統(tǒng)調(diào)用废亭,會觸發(fā)內(nèi)核狀態(tài)切換国章。在用戶態(tài)調(diào)用 mach_msg_trap()時會切換到內(nèi)核態(tài);內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的mach_msg()函數(shù)會完成實(shí)際的工作豆村。
即基于port的source1液兽,監(jiān)聽端口,端口有消息就會觸發(fā)回調(diào)掌动;而source0四啰,要手動標(biāo)記為待處理和手動喚醒RunLoop

Mach消息發(fā)送機(jī)制
大致邏輯為:
1、通知觀察者 RunLoop 即將啟動粗恢。
2柑晒、通知觀察者即將要處理Timer事件。
3眷射、通知觀察者即將要處理source0事件匙赞。
4、處理source0事件妖碉。
5涌庭、如果基于端口的源(Source1)準(zhǔn)備好并處于等待狀態(tài),進(jìn)入步驟9嗅绸。
6脾猛、通知觀察者線程即將進(jìn)入休眠狀態(tài)。
7鱼鸠、將線程置于休眠狀態(tài)猛拴,由用戶態(tài)切換到內(nèi)核態(tài),直到下面的任一事件發(fā)生才喚醒線程蚀狰。

  • 一個基于 port 的Source1 的事件(圖里應(yīng)該是source0)愉昆。
  • 一個 Timer 到時間了。
  • RunLoop 自身的超時時間到了麻蹋。
  • 被其他調(diào)用者手動喚醒跛溉。

8、通知觀察者線程將被喚醒。
9芳室、處理喚醒時收到的事件专肪。

  • 如果用戶定義的定時器啟動,處理定時器事件并重啟RunLoop堪侯。進(jìn)入步驟2嚎尤。
  • 如果輸入源啟動,傳遞相應(yīng)的消息伍宦。
  • 如果RunLoop被顯示喚醒而且時間還沒超時芽死,重啟RunLoop。進(jìn)入步驟2

10次洼、通知觀察者RunLoop結(jié)束关贵。

五、RunLoop與NSTimer

一個比較常見的問題:滑動tableView時卖毁,定時器還會生效嗎揖曾?
默認(rèn)情況下RunLoop運(yùn)行在kCFRunLoopDefaultMode下,而當(dāng)滑動tableView時势篡,RunLoop切換到UITrackingRunLoopMode翩肌,而Timer是在kCFRunLoopDefaultMode下的模暗,就無法接受處理Timer的事件禁悠。
怎么去解決這個問題呢?把Timer添加到UITrackingRunLoopMode上并不能解決問題兑宇,因?yàn)檫@樣在默認(rèn)情況下就無法接受定時器事件了碍侦。
所以我們需要把Timer同時添加到UITrackingRunLoopModekCFRunLoopDefaultMode上。
那么如何把timer同時添加到多個mode上呢隶糕?就要用到NSRunLoopCommonModes

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

Timer就被添加到多個mode上瓷产,這樣即使RunLoop由kCFRunLoopDefaultMode切換到UITrackingRunLoopMode下,也不會影響接收Timer事件

六枚驻、RunLoop和線程

  • 線程和RunLoop是一一對應(yīng)的,其映射關(guān)系是保存在一個全局的 Dictionary 里
  • 自己創(chuàng)建的線程默認(rèn)是沒有開啟RunLoop的

1濒旦、怎么創(chuàng)建一個常駐線程?

1再登、為當(dāng)前線程開啟一個RunLoop(第一次調(diào)用 [NSRunLoop currentRunLoop]方法時實(shí)際是會先去創(chuàng)建一個RunLoop)
1尔邓、向當(dāng)前RunLoop中添加一個Port/Source等維持RunLoop的事件循環(huán)(如果RunLoop的mode中一個item都沒有,RunLoop會退出)
2锉矢、啟動該RunLoop

   @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }

2梯嗽、輸出下邊代碼的執(zhí)行順序

 NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"2");
    [self performSelector:@selector(test) withObject:nil afterDelay:10];
    NSLog(@"3");
});
NSLog(@"4");
- (void)test
{
    NSLog(@"5");
}

答案是1423,test方法并不會執(zhí)行雅潭。
原因是如果是帶afterDelay的延時函數(shù)心傀,會在內(nèi)部創(chuàng)建一個 NSTimer史隆,然后添加到當(dāng)前線程的RunLoop中爆捞。也就是如果當(dāng)前線程沒有開啟RunLoop炎疆,該方法會失效卡骂。
那么我們改成:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        [[NSRunLoop currentRunLoop] run];
        [self performSelector:@selector(test) withObject:nil afterDelay:10];
        NSLog(@"3");
    });

然而test方法依然不執(zhí)行。
原因是如果RunLoop的mode中一個item都沒有形入,RunLoop會退出偿警。即在調(diào)用RunLoop的run方法后,由于其mode中沒有添加任何item去維持RunLoop的時間循環(huán)唯笙,RunLoop隨即還是會退出螟蒸。
所以我們自己啟動RunLoop,一定要在添加item后

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        [self performSelector:@selector(test) withObject:nil afterDelay:10];
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"3");
    });

3崩掘、怎樣保證子線程數(shù)據(jù)回來更新UI的時候不打斷用戶的滑動操作七嫌?

當(dāng)我們在子請求數(shù)據(jù)的同時滑動瀏覽當(dāng)前頁面,如果數(shù)據(jù)請求成功要切回主線程更新UI苞慢,那么就會影響當(dāng)前正在滑動的體驗(yàn)诵原。
我們就可以將更新UI事件放在主線程的NSDefaultRunLoopMode上執(zhí)行即可,這樣就會等用戶不再滑動頁面挽放,主線程RunLoop由UITrackingRunLoopMode切換到NSDefaultRunLoopMode時再去更新UI

[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];

收錄:原文地址


更多:iOS面試題合集

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绍赛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辑畦,更是在濱河造成了極大的恐慌吗蚌,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纯出,死亡現(xiàn)場離奇詭異蚯妇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)暂筝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門箩言,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人焕襟,你說我怎么就攤上這事陨收。” “怎么了鸵赖?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵务漩,是天一觀的道長。 經(jīng)常有香客問我卫漫,道長菲饼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任列赎,我火速辦了婚禮宏悦,結(jié)果婚禮上镐确,老公的妹妹穿的比我還像新娘。我一直安慰自己饼煞,他們只是感情好源葫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砖瞧,像睡著了一般息堂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上块促,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天荣堰,我揣著相機(jī)與錄音,去河邊找鬼竭翠。 笑死振坚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斋扰。 我是一名探鬼主播渡八,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼传货!你這毒婦竟也來了屎鳍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤问裕,失蹤者是張志新(化名)和其女友劉穎逮壁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僻澎,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡貌踏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年十饥,在試婚紗的時候發(fā)現(xiàn)自己被綠了窟勃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡逗堵,死狀恐怖秉氧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜒秤,我是刑警寧澤汁咏,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站作媚,受9級特大地震影響攘滩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纸泡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一漂问、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蚤假、人聲如沸栏饮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袍嬉。三九已至,卻和暖如春灶平,著一層夾襖步出監(jiān)牢的瞬間伺通,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工逢享, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泵殴,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓拼苍,卻偏偏與公主長得像笑诅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疮鲫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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

  • 轉(zhuǎn)自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飄金閱讀 976評論 0 4
  • Runloop是iOS和OSX開發(fā)中非尺耗悖基礎(chǔ)的一個概念,從概念開始學(xué)習(xí)俊犯。 RunLoop的概念 -般說妇多,一個線程一...
    小貓仔閱讀 985評論 0 1
  • 前言 RunLoop是iOS和OSX開發(fā)中非常基礎(chǔ)的一個概念燕侠,這篇文章將從CFRunLoop的源碼入手者祖,介紹Run...
    暮年古稀ZC閱讀 2,232評論 1 19
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,436評論 0 13
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術(shù) RunLoop 是 iOS 和 ...
    橙娃閱讀 849評論 1 2