一癌蚁、概念
RunLoop是通過內(nèi)部維護的事件循環(huán)來對事件/消息進行管理的一個對象。
問題1:什么是事件循環(huán)
解釋:
-
沒有消息需要處理時罗心,休眠以避免資源占用吏砂。
-
有消息處理時,立刻被喚醒拳魁。
備注:
內(nèi)核態(tài):在一個進程中惶桐,如果有系統(tǒng)調(diào)用,此時進程處于內(nèi)核態(tài)潘懊;系統(tǒng)操作包括:執(zhí)行文件操作姚糊,網(wǎng)絡(luò)數(shù)據(jù)發(fā)送等操作,此時特權(quán)級別比較高授舟,0級救恨。
用戶態(tài):當一個進程執(zhí)行用戶自己的代碼時,處于用戶態(tài)释树,此時特權(quán)級別比較低肠槽,3級。
小結(jié):
所有用戶程序都是運行在用戶態(tài)的, 但是有時候程序確實需要做一些內(nèi)核態(tài)的事情, 例如執(zhí)行文件操作躏哩,網(wǎng)絡(luò)數(shù)據(jù)發(fā)送等操作署浩。
而唯一可以做這些事情的就是操作系統(tǒng), 所以此時程序就需要先操作系統(tǒng)請求以程序的名義來執(zhí)行這些操作揉燃。
以下是操作系統(tǒng)內(nèi)存空間分布圖:
問題2:為什么main函數(shù)能夠保證不退出
解釋:
1扫尺、在main函數(shù)當中,調(diào)用了UIApplicationMain函數(shù)炊汤。
2正驻、UIApplicationMain函數(shù)會啟動主線程的runloop。
3抢腐、runloop是一個通過事件循環(huán)處理事件/消息的對象姑曙,可以做到“有事做的時候去做事,沒事做的時候從用戶態(tài)切換到內(nèi)核態(tài)迈倍,避免資源的占用伤靠,當前線程是處于一個休眠狀態(tài)”。
//main函數(shù)代碼
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
二啼染、runloop數(shù)據(jù)結(jié)構(gòu)相關(guān)
2.1宴合、runloop相關(guān)框架
NSRunLoop是CFRunLoop的封裝焕梅,提供了面向?qū)ο蟮腁PI。
2.2卦洽、runloop主要結(jié)構(gòu)
runloop相關(guān)的數(shù)據(jù)結(jié)構(gòu):
- CFRunLoop
- CFRunLoopMode
- Source/Timer/Observer
2.2.1贞言、CFRunLoop
主要包含5個部分
pthread
和線程一一對應(yīng)(RunLoop和線程關(guān)系)currentMode
CFRunLoopMode類型-
modes
NSMutableSet<CFRunLoopMode*>
mode有多個類型- NSDefaultRunLoopMode
默認mode模型、主線程就在這個模型下 - UITrackingRunLoopMode
界面跟蹤mode阀蒂、滑動列表界面 - NSRunLoopCommonModes
CommonMode不是實際存在的一種Mode该窗。
是同步Source/Timer/Observer到多個Model中的一種技術(shù)方案。 - UIInitializationRunLoopMode
初始化mode(沒用過) - GSEventReceiveRunLoopMode
系統(tǒng)內(nèi)部mode(沒用過)
- NSDefaultRunLoopMode
commonModes
NSMutableSet<NSString*>
被打上“common”標記的mode的名稱集合 == NSDefaultRunLoopMode + UITrackingRunLoopModecmomonModelItems
Source/Timer/Observer
如果當前模式是commonModes蚤霞,則會處理cmomonModelItems里面的 Source/Timer/Observer事件酗失。
問題3:RunLoop和線程之間的關(guān)系
解釋:
1、線程和RunLoop之間的關(guān)系是一一對應(yīng)的昧绣。
2级零、主線程的RunLoop是默認開啟的,子線程的RunLoop是默認不開啟的滞乙。
3奏纪、子線程的RunLoop在你主動獲取的情況下,才會創(chuàng)建斩启。
子線程的RunLoop在線程結(jié)束時序调,才會銷毀
主線程除外。
問題4:commonModes的作用
解釋:
- CommonMode不是實際存在的一種Mode兔簇。
- 是同步Source/Timer/Observer到多個Model中的一種技術(shù)方案发绢。
2.2.2、CFRunLoopMode
主要包含以下部分:
-
name
名稱垄琐、默認是NSDefaultRunLoopMode边酒。 -
source0
NSMutableSet -
source1
NSMutableSet -
observers
NSMutableArray -
timers
NSMutableArray
2.2.3、CFRunLoopSource
-
source0
需要手動喚醒線程 -
source1
具備喚醒線程的能力
問題5:source0和source1有什么樣的區(qū)別
解釋
source0需要手動喚醒線程狸窘。
source1具備喚醒線程的能力墩朦。
2.2.3、CFRunLoopTimer
基于事件的定時器
和NSTimer是toll-free bridged (免費橋轉(zhuǎn)換)翻擒。
2.2.4氓涣、CFRunLoopObserver
觀測時間點(共6中狀態(tài))
- kCFRunLoopEntry
即將進入Loop - kCFRunLoopBeforeTimers
即將處理 Timer - kCFRunLoopBeforeSources
即將處理 Source - kCFRunLoopBeforeWaiting
即將進入休眠 - kCFRunLoopAfterWaiting
剛從休眠中喚醒 - kCFRunLoopExit
即將退出Loop
2.2.5、各個數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系
問題6 RunLoop陋气、Model劳吠、Source/Timer/Observer關(guān)系
解釋
RunLoop和Model是一對多的關(guān)系(從CFRunLoop的數(shù)據(jù)結(jié)構(gòu)modes)。
Model和Source/Timer/Observer是一對多的關(guān)系巩趁。
問題7 RunLoop為什么有多個Model
解釋
這樣設(shè)計的原因是為了起到事件屏蔽的效果痒玩。
當RunLoop運行在Mode1上時,只能接收處理Mode1上的source1、observers蠢古、timers事件回調(diào)燃观,不能接收其它Mode上的source/observer/timer事件回調(diào);起到了事件屏蔽的效果便瑟。
2.2.6缆毁、CommonMode的特殊性
在ios上對應(yīng)的是NSRunLoopCommonModes。
- CommonMode不是實際存在的一種Mode到涂。
- 是同步Source/Timer/Observer到多個Model中的一種技術(shù)方案脊框。
三、事件循環(huán)機制(內(nèi)部邏輯)
3.1践啄、整體流程
1浇雹、在RunLoop啟動后,會發(fā)送一個通知屿讽,告知Observer觀察者昭灵。
2、將要處理Timer/Source0事件伐谈,會發(fā)送一個通知烂完,告知Observer觀察者。
3诵棵、處理Source0事件抠蚣。
4、如果有Source1要處理履澳,會跳過當前流程嘶窄,到第8步。
5距贷、沒有Source1要處理柄冲,線程將要休眠,發(fā)送通知忠蝗。
6现横、休眠,等待喚醒什湘。
7长赞、線程剛被喚醒晦攒。
8闽撤、處理喚醒時收到的消息。
問題8 當一個處于休眠狀態(tài)RunLoop通過哪些事件喚醒它
解釋
- Source1回調(diào)
- Timer事件
- 外部手動喚醒
小結(jié):
點擊App圖標脯颜,從程序啟動哟旗、運行、退出這個過程講解,系統(tǒng)都發(fā)生了什么闸餐?
解釋
1饱亮、程序啟動后,調(diào)用main函數(shù)后舍沙,會調(diào)用UIApplicationmain近上,這UIApplicationmain函數(shù)內(nèi)部會啟動主線程的RunLoop。經(jīng)過一系列處理拂铡,最終主線程RunLoop處于休眠狀態(tài)壹无。
2、此時感帅,點擊一個屏幕斗锭,會產(chǎn)生一個mach_port,基于mach_port最終會轉(zhuǎn)換成一個Source1失球,然后可以喚醒主線程岖是,運行處理事件。
3实苞、當把程序殺死時候豺撑,就會發(fā)生退出RunLoop,發(fā)送通知即將退出RunLoop黔牵,RunLoop退出后前硫,線程也就銷毀掉了。
3.2荧止、RunLoop核心
1屹电、在main函數(shù)中,經(jīng)過一系列處理跃巡,會調(diào)用系統(tǒng)函數(shù)mach_msg()危号,就發(fā)生了系統(tǒng)調(diào)用,這樣會從用戶態(tài)到核心態(tài)
2素邪、在核心態(tài)下面外莲,在一定條件下(Source1/Timer/外部手動喚醒),mach_msg()兔朦,會返回給調(diào)用方偷线,也就是程序從核心態(tài)到用戶態(tài)。
四沽甥、RunLoop與NSTimer
問題9 滑動TableView的時候我們的定時器還會生效嗎声邦?
不會生效
原因:
1、當我們滑動scrollView時摆舟,主線程的RunLoop 會切換到UITrackingRunLoopMode這個Mode亥曹,執(zhí)行的也是UITrackingRunLoopMode下的任務(wù)(Mode中的Source/Timer/Observer)邓了。
2、而timer 是添加在NSDefaultRunLoopMode下的媳瞪,所以timer任務(wù)并不會執(zhí)行骗炉,只有當UITrackingRunLoopMode的任務(wù)執(zhí)行完畢,runloop切換到NSDefaultRunLoopMode后蛇受,才會繼續(xù)執(zhí)行timer句葵。
解決問題:
我們只需要在添加timer 時,將mode 設(shè)置為NSRunLoopCommonModes即可兢仰。
五笼呆、RunLoop與多線程
線程和RunLoop是一一對應(yīng)的
自己創(chuàng)建的線程默認沒有RunLoop的
問題10 怎么實現(xiàn)一個常駐線程
- 為當前線程開啟一個RunLoop
- 向RunLoop中添加一個Port/Source等維持RunLoop的事件循環(huán)
- 啟動該RunLoop
//創(chuàng)建線程
HLThread *subThread = [[HLThread alloc] initWithTarget:self selector:@selector(subThreadEntryPoint) object:nil];
[subThread setName:@"HLThread"];
[subThread start];
//開啟RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//如果注釋了下面這一行,子線程中的任務(wù)并不能正常執(zhí)行
[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[runLoop run];
六旨别、面試問題總結(jié)
問題11诗赌、什么是RunLoop,它是怎樣做到有事做事秸弛,沒事休息的铭若?
1、RunLoop是一個通過內(nèi)部循環(huán)對事件/消息進行管理的一個對象递览。
2叼屠、程序運行會調(diào)用main函數(shù),在main函數(shù)里面調(diào)用UIApplicationMain绞铃,UIApplicationMain函數(shù)會啟動主線程的runloop镜雨。
3、runloop運行后儿捧,會調(diào)用系統(tǒng)方法mach_msg()荚坞,會使得程序從用戶態(tài)變成核心態(tài),此時線程處于休眠狀態(tài)菲盾。
4颓影、當有外界條件變化(Source/Timer/Observer),mach_msg會使得程序從核心態(tài)變成用戶態(tài)懒鉴,此時線程處于活躍狀態(tài)诡挂。
問題12、RunLoop與線程是怎么樣的關(guān)系
1临谱、RunLoop與線程是一一對應(yīng)的關(guān)系璃俗。
2、一個線程默認是沒有runloop的(主線程除外)悉默。
問題13城豁、如何實現(xiàn)一個常駐線程
1、為當前線程開啟一個RunLoop
2麦牺、向RunLoop中添加一個Port/Source等維持RunLoop的事件循環(huán)
3钮蛛、啟動該RunLoop
問題14鞭缭、怎樣保證子線程數(shù)據(jù)回來更新UI的時候剖膳,不打斷用戶的滑動操作魏颓?
1、把子線程拋給主線程進行UI更新的邏輯吱晒,可以包裝起來甸饱,提交到主線程的NSDefaultRunLoopMode模式下面。
2仑濒、因為用戶滑動操作是在UITrackingRunLoopMode模式下進行的叹话。
//參考代碼事件
[self.tableView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];