What's Run Loops?
其實就是跑圈的意思枚尼,就像下面這張圖
那么RunLoop具體解決什么問題呢庭惜?
首先看一下下面普通的代碼院峡,就是寫一個 Hello world
int main(int argc, char *argc[]) {
NSLog(@"Hello world");
return 0;
}
上面的代碼呢是從 main 函數(shù)開始執(zhí)行窖式, 到return 0 之后就在查看日志的地方打印一行 Hello world港柜。然后就退出了刘陶, 它是一個命令式線性執(zhí)行的一個過程胳赌。
** 但是咱們的手機應(yīng)用肯定不能說上來執(zhí)行完代碼之后就完了,結(jié)束了匙隔。它得一直去保持接收用戶消息疑苫、或者處理一些什么事情啊的這么一個狀態(tài),它必須一直去活著纷责。等到用戶把它干掉才能退出捍掺。**
那咱們再來一段偽代碼:
int main(int argc, char *argc[]) {
while (AppIsRunning) {
id whoWakesMe = SleepForWakingUp();
id event = GetEvent(whoWakesMe);
HandleEvent(event);
}
}
上面的代碼可以歸納為 event 驅(qū)動,即咱們手機是一個事件驅(qū)動的這么一個架構(gòu)再膳。要實現(xiàn)這個event驅(qū)動的方法是挺勿,它的主體是一個死循環(huán)就行了。while這個程序還在跑喂柒,如果沒事的話不瓶,它就去睡覺,假如有事灾杰,有人把它喚醒蚊丐,去取一下是什么事件把它喚醒,然后去分配這個事件艳吠,去執(zhí)行就ok了麦备。
好了, 現(xiàn)在總結(jié)一下昭娩, 為什么要有這個RunLoop呢凛篙?
- 必須要讓這個程序一直活著,使程序一直運行并接受用戶輸入或者網(wǎng)絡(luò)的輸入栏渺,程序自己還得有輸出呛梆,必須要一直保持和用戶交互的這么一個狀態(tài)
- 決定程序在何時應(yīng)該處理那些Event
- 調(diào)用解耦(Message Queue)
那么什么是解耦呢?
主調(diào)方(也就是用戶或者其他)把Event扔到消息隊列里迈嘹,不管了.
然后被調(diào)方(處理UI事件的這一方)每一次去消息隊列里取event削彬,取完之后是自己出派發(fā)呀或者是執(zhí)行某些代碼.
跟主調(diào)方可以實現(xiàn)一個解耦.
- 節(jié)省CPU時間
下面我們來看一下RunLoop構(gòu)成元素
從圖中可以看到全庸, RunLoop 和 線程(Thread)是一一對應(yīng)的關(guān)系秀仲。
其他的對應(yīng)關(guān)系也就可以看明白了融痛。
- CFRunLoopTimer
咱們平時用到的一些關(guān)于time的方法都是關(guān)于RunLoopTimer的封裝 - CFRunLoopSource
Source 是RunLoop的數(shù)據(jù)源抽象類(protocol)
1 Source0: 處理App內(nèi)部事件,App自己負責管理(觸發(fā))如UIEvent、CFSocket
2 Source1: 由RunLoop和內(nèi)核管理, Mach port 驅(qū)動,如CFMessagePort
如果有需要, 可以從中選擇一種自己的Source
上一條基本不會發(fā)生 - CFRunLoopObserver
向外部報告RunLoop當前狀態(tài)的更改
框架中很多機制都由RunLoopObserver觸發(fā), 如CAAnimation
RunLoopObserver 與 AutoreleasePool的關(guān)系:
UIKit通過在 RunLoop兩次Sleep間對AutoreleasePool進行Pop和Push將這次Loop中產(chǎn)生Autorelease對象釋放
- CFRunLoopMode
RunLoop在同一段時間只能且必須在一種特定Mode下Run
更換Mode時,需要停止當前Loop, 然后重啟新Loop
Mode是iOS App滑動順暢的關(guān)鍵
可以定制自己的mode(當然這一點通常情況下也是不會發(fā)生的)
1 NSDefaultRunLoopMode 默認狀態(tài)神僵,空閑狀態(tài)
2 UITrackingRunLoopMode 滑動ScrollView時
3 UIInitializationRunLoopMode 私有App啟動時執(zhí)行
4 NSRunLoopCommonModes 在這個Mode下, 1和2都可以執(zhí)行是Mode 集合
** RunLoop與dispatch_get_main_queue()的關(guān)系:**
GCD中diapatch到main queue的block被分到main RunLoop執(zhí)行
dispatch_after同理
- RunLoop的掛起和喚醒
1 等待的時候先指定用于喚醒的 mach_port端口雁刷,然后可以睡覺了
2 調(diào)用mach_msg 監(jiān)聽喚醒端口,被喚醒前,系統(tǒng)內(nèi)核將這個線程掛起,停留在mach_msg_trap狀態(tài)
3 由另一個線程(或另一個進程中的某個線程)向內(nèi)核發(fā)送這個端口msg后,trap狀態(tài)被喚醒,RunLoop繼續(xù)開始干活
** RunLoop迭代執(zhí)行順序**
寫一段偽代碼總結(jié)一下do while里干的事
SetupThisRunLoopTimeOutTimer(); // by GCD timer. do while 之前先得有一個RunLoop的過期時間, 這樣它并不是一個正真的死循環(huán),而是有一個很長很長的time不可能執(zhí)行完保礼。
do {
__CFRunLoopCoObservers[kCFRunLoopBeforeTimers]; // 告訴observers 我要跑timer了
__CFRunLoopDoObservers[kCFRunLoopBeforeSources]; // 告訴observers 我要跑 source了
__CFRunLoopDoBlocks(); // 跑哪6個其中一個
__CFRunLoopDoSource0(); // 跑source0
// 跑到這的時候沛励,程序先向source0中看一下在消息列隊中有沒有什么可以分派或者執(zhí)行的消息
// 如果有,這個里面會遍歷source0去跑
CheckIfExistMessagesInMainDispatchQueue(); // 然后去問一下GCD你有沒有分到主線程的東西需要我?guī)湍阏{(diào)
__CFRunLoopDoObservers[kCFRunLoopBeforeWaiting]; // 開始睡覺
var wakeUpPort = SleepAndWaitingForWakingUpPorts(); // 知道是哪個端口把我喚醒的
__CFRunLoopDoObservers[kCFRunLoopAfterWaiting]; // 醒來之后告訴Observer 是哪個端口喚醒我的
if (waitingUpPort == timePort) { // 如果是timer把我喚醒的就去跑timer的事件
__CFRunLoopDoTimers();
} else if (wakeUpPort == mainDispatchQueue) { // 如果是主線程的GCD把你喚醒的炮障,就去幫GCD干事了
__CFRunLoop_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE_();
} else { // 如果都不是的話目派,那就跑source1,source1是基于port事件胁赢。
__CFRunLoopDoSource1();
}
__CFRunLoopDoBlacks();
// 這就是一圈RunLoop要跑的代碼
} while (!stop && !timeout);
OK.