RunLoop知識(shí)點(diǎn)

一、概念

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

問題1:什么是事件循環(huán)

解釋:

  • 沒有消息需要處理時(shí),休眠以避免資源占用。


    圖1 線程狀態(tài)切換
  • 有消息處理時(shí),立刻被喚醒。


    圖2 線程狀態(tài)切換

備注:
內(nèi)核態(tài):在一個(gè)進(jìn)程中,如果有系統(tǒng)調(diào)用,此時(shí)進(jìn)程處于內(nèi)核態(tài);系統(tǒng)操作包括:執(zhí)行文件操作,網(wǎng)絡(luò)數(shù)據(jù)發(fā)送等操作,此時(shí)特權(quán)級(jí)別比較高,0級(jí)。

用戶態(tài):當(dāng)一個(gè)進(jìn)程執(zhí)行用戶自己的代碼時(shí),處于用戶態(tài),此時(shí)特權(quán)級(jí)別比較低,3級(jí)。

小結(jié):
所有用戶程序都是運(yùn)行在用戶態(tài)的, 但是有時(shí)候程序確實(shí)需要做一些內(nèi)核態(tài)的事情, 例如執(zhí)行文件操作,網(wǎng)絡(luò)數(shù)據(jù)發(fā)送等操作。
而唯一可以做這些事情的就是操作系統(tǒng), 所以此時(shí)程序就需要先操作系統(tǒng)請(qǐng)求以程序的名義來執(zhí)行這些操作。

以下是操作系統(tǒng)內(nèi)存空間分布圖:

操作系統(tǒng)分布圖

問題2:為什么main函數(shù)能夠保證不退出

解釋:
1、在main函數(shù)當(dāng)中,調(diào)用了UIApplicationMain函數(shù)。
2、UIApplicationMain函數(shù)會(huì)啟動(dòng)主線程的runloop。
3、runloop是一個(gè)通過事件循環(huán)處理事件/消息的對(duì)象,可以做到“有事做的時(shí)候去做事,沒事做的時(shí)候從用戶態(tài)切換到內(nèi)核態(tài),避免資源的占用,當(dāng)前線程是處于一個(gè)休眠狀態(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)框架

圖3 runloop所在框架

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個(gè)部分

  • pthread
    和線程一一對(duì)應(yīng)(RunLoop和線程關(guān)系)

  • currentMode
    CFRunLoopMode類型

  • modes
    NSMutableSet<CFRunLoopMode*>
    mode有多個(gè)類型

    • NSDefaultRunLoopMode
      默認(rèn)mode模型、主線程就在這個(gè)模型下
    • UITrackingRunLoopMode
      界面跟蹤mode、滑動(dòng)列表界面
    • NSRunLoopCommonModes
      CommonMode不是實(shí)際存在的一種Mode。
      是同步Source/Timer/Observer到多個(gè)Model中的一種技術(shù)方案。
    • UIInitializationRunLoopMode
      初始化mode(沒用過)
    • GSEventReceiveRunLoopMode
      系統(tǒng)內(nèi)部mode(沒用過)
  • commonModes
    NSMutableSet<NSString*>
    被打上“common”標(biāo)記的mode的名稱集合 == NSDefaultRunLoopMode + UITrackingRunLoopMode

  • cmomonModelItems
    Source/Timer/Observer
    如果當(dāng)前模式是commonModes,則會(huì)處理cmomonModelItems里面的 Source/Timer/Observer事件。

問題3:RunLoop和線程之間的關(guān)系

解釋:
1、線程和RunLoop之間的關(guān)系是一一對(duì)應(yīng)的。
2、主線程的RunLoop是默認(rèn)開啟的,子線程的RunLoop是默認(rèn)不開啟的。
3、子線程的RunLoop在你主動(dòng)獲取的情況下,才會(huì)創(chuàng)建。
子線程的RunLoop在線程結(jié)束時(shí),才會(huì)銷毀
主線程除外。

問題4:commonModes的作用

解釋:

  • CommonMode不是實(shí)際存在的一種Mode。
  • 是同步Source/Timer/Observer到多個(gè)Model中的一種技術(shù)方案。

2.2.2、CFRunLoopMode

主要包含以下部分:

  • name
    名稱、默認(rèn)是NSDefaultRunLoopMode。
  • source0
    NSMutableSet
  • source1
    NSMutableSet
  • observers
    NSMutableArray
  • timers
    NSMutableArray

2.2.3、CFRunLoopSource

  • source0
    需要手動(dòng)喚醒線程
  • source1
    具備喚醒線程的能力

問題5:source0和source1有什么樣的區(qū)別

解釋
source0需要手動(dòng)喚醒線程。
source1具備喚醒線程的能力。

2.2.3、CFRunLoopTimer

基于事件的定時(shí)器
和NSTimer是toll-free bridged (免費(fèi)橋轉(zhuǎn)換)。

2.2.4、CFRunLoopObserver

觀測(cè)時(shí)間點(diǎn)(共6中狀態(tài))

  • kCFRunLoopEntry
    即將進(jìn)入Loop
  • kCFRunLoopBeforeTimers
    即將處理 Timer
  • kCFRunLoopBeforeSources
    即將處理 Source
  • kCFRunLoopBeforeWaiting
    即將進(jìn)入休眠
  • kCFRunLoopAfterWaiting
    剛從休眠中喚醒
  • kCFRunLoopExit
    即將退出Loop

2.2.5、各個(gè)數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系

圖4 RunLoop結(jié)構(gòu)關(guān)系

問題6 RunLoop、Model、Source/Timer/Observer關(guān)系

解釋
RunLoop和Model是一對(duì)多的關(guān)系(從CFRunLoop的數(shù)據(jù)結(jié)構(gòu)modes)。
Model和Source/Timer/Observer是一對(duì)多的關(guān)系。

問題7 RunLoop為什么有多個(gè)Model

解釋
這樣設(shè)計(jì)的原因是為了起到事件屏蔽的效果。

圖5 Model

當(dāng)RunLoop運(yùn)行在Mode1上時(shí),只能接收處理Mode1上的source1、observers、timers事件回調(diào),不能接收其它Mode上的source/observer/timer事件回調(diào);起到了事件屏蔽的效果。

2.2.6、CommonMode的特殊性

在ios上對(duì)應(yīng)的是NSRunLoopCommonModes。

  • CommonMode不是實(shí)際存在的一種Mode。
  • 是同步Source/Timer/Observer到多個(gè)Model中的一種技術(shù)方案。

三、事件循環(huán)機(jī)制(內(nèi)部邏輯)

3.1、整體流程

圖6 事件循環(huán)

1、在RunLoop啟動(dòng)后,會(huì)發(fā)送一個(gè)通知,告知Observer觀察者。
2、將要處理Timer/Source0事件,會(huì)發(fā)送一個(gè)通知,告知Observer觀察者。
3、處理Source0事件。
4、如果有Source1要處理,會(huì)跳過當(dāng)前流程,到第8步。
5、沒有Source1要處理,線程將要休眠,發(fā)送通知。
6、休眠,等待喚醒。
7、線程剛被喚醒。
8、處理喚醒時(shí)收到的消息。

問題8 當(dāng)一個(gè)處于休眠狀態(tài)RunLoop通過哪些事件喚醒它

解釋

  • Source1回調(diào)
  • Timer事件
  • 外部手動(dòng)喚醒

小結(jié):

點(diǎn)擊App圖標(biāo),從程序啟動(dòng)、運(yùn)行、退出這個(gè)過程講解,系統(tǒng)都發(fā)生了什么?

解釋
1、程序啟動(dòng)后,調(diào)用main函數(shù)后,會(huì)調(diào)用UIApplicationmain,這UIApplicationmain函數(shù)內(nèi)部會(huì)啟動(dòng)主線程的RunLoop。經(jīng)過一系列處理,最終主線程RunLoop處于休眠狀態(tài)。
2、此時(shí),點(diǎn)擊一個(gè)屏幕,會(huì)產(chǎn)生一個(gè)mach_port,基于mach_port最終會(huì)轉(zhuǎn)換成一個(gè)Source1,然后可以喚醒主線程,運(yùn)行處理事件。
3、當(dāng)把程序殺死時(shí)候,就會(huì)發(fā)生退出RunLoop,發(fā)送通知即將退出RunLoop,RunLoop退出后,線程也就銷毀掉了。

3.2、RunLoop核心

圖7 RunLoop核心

1、在main函數(shù)中,經(jīng)過一系列處理,會(huì)調(diào)用系統(tǒng)函數(shù)mach_msg(),就發(fā)生了系統(tǒng)調(diào)用,這樣會(huì)從用戶態(tài)到核心態(tài)
2、在核心態(tài)下面,在一定條件下(Source1/Timer/外部手動(dòng)喚醒),mach_msg(),會(huì)返回給調(diào)用方,也就是程序從核心態(tài)到用戶態(tài)。

四、RunLoop與NSTimer

問題9 滑動(dòng)TableView的時(shí)候我們的定時(shí)器還會(huì)生效嗎?

不會(huì)生效
原因:
1、當(dāng)我們滑動(dòng)scrollView時(shí),主線程的RunLoop 會(huì)切換到UITrackingRunLoopMode這個(gè)Mode,執(zhí)行的也是UITrackingRunLoopMode下的任務(wù)(Mode中的Source/Timer/Observer)。
2、而timer 是添加在NSDefaultRunLoopMode下的,所以timer任務(wù)并不會(huì)執(zhí)行,只有當(dāng)UITrackingRunLoopMode的任務(wù)執(zhí)行完畢,runloop切換到NSDefaultRunLoopMode后,才會(huì)繼續(xù)執(zhí)行timer。

解決問題:
我們只需要在添加timer 時(shí),將mode 設(shè)置為NSRunLoopCommonModes即可。

五、RunLoop與多線程

線程和RunLoop是一一對(duì)應(yīng)的
自己創(chuàng)建的線程默認(rèn)沒有RunLoop的

問題10 怎么實(shí)現(xiàn)一個(gè)常駐線程

  • 為當(dāng)前線程開啟一個(gè)RunLoop
  • 向RunLoop中添加一個(gè)Port/Source等維持RunLoop的事件循環(huán)
  • 啟動(dòng)該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是一個(gè)通過內(nèi)部循環(huán)對(duì)事件/消息進(jìn)行管理的一個(gè)對(duì)象。
2、程序運(yùn)行會(huì)調(diào)用main函數(shù),在main函數(shù)里面調(diào)用UIApplicationMain,UIApplicationMain函數(shù)會(huì)啟動(dòng)主線程的runloop。
3、runloop運(yùn)行后,會(huì)調(diào)用系統(tǒng)方法mach_msg(),會(huì)使得程序從用戶態(tài)變成核心態(tài),此時(shí)線程處于休眠狀態(tài)。
4、當(dāng)有外界條件變化(Source/Timer/Observer),mach_msg會(huì)使得程序從核心態(tài)變成用戶態(tài),此時(shí)線程處于活躍狀態(tài)。

問題12、RunLoop與線程是怎么樣的關(guān)系

1、RunLoop與線程是一一對(duì)應(yīng)的關(guān)系。
2、一個(gè)線程默認(rèn)是沒有runloop的(主線程除外)。

問題13、如何實(shí)現(xiàn)一個(gè)常駐線程

1、為當(dāng)前線程開啟一個(gè)RunLoop
2、向RunLoop中添加一個(gè)Port/Source等維持RunLoop的事件循環(huán)
3、啟動(dòng)該RunLoop

問題14、怎樣保證子線程數(shù)據(jù)回來更新UI的時(shí)候,不打斷用戶的滑動(dòng)操作?

1、把子線程拋給主線程進(jìn)行UI更新的邏輯,可以包裝起來,提交到主線程的NSDefaultRunLoopMode模式下面。
2、因?yàn)橛脩艋瑒?dòng)操作是在UITrackingRunLoopMode模式下進(jìn)行的。

//參考代碼事件
[self.tableView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,558評(píng)論 0 13
  • 轉(zhuǎn)自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_閱讀 1,682評(píng)論 0 5
  • RunLoop 的概念 一般來講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線...
    Mirsiter_魏閱讀 671評(píng)論 0 2
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術(shù) RunLoop 是 iOS 和 ...
    橙娃閱讀 967評(píng)論 1 2
  • https://blog.ibireme.com/2015/05/18/runloop/ RunLoop 是 iO...
    SmallDe閱讀 777評(píng)論 0 51

友情鏈接更多精彩內(nèi)容