我們常常會(huì)遇到或者被問到,NSRunloop到底是什么以及有什么作用击喂?作為一枚iOS開發(fā)的老菜鳥......我是這樣理解的:
如果我們子線程需要依賴另一事務(wù)(通常是另一線程)的某個(gè)狀態(tài)维苔,為了不讓子線程一直處于while循環(huán)(親身經(jīng)歷:有while死循環(huán)時(shí)退后臺(tái)會(huì)時(shí)CPU占用增加而導(dǎo)致發(fā)熱...),又想讓線程一直運(yùn)行......
那么NSRunloop就是為了倍海活我們子線程而存在的介时,它在接受一些特定事件后能夠”喚醒“線程,處理完畢后再讓線程”休眠“凌彬,這樣就能合理的調(diào)控系統(tǒng)資源沸柔。主線程能夠接收到ui以及其他事件,也是通過rl來傳遞的铲敛,主線程的runloop是默認(rèn)開啟的褐澎,而子線程的runloop需要我們”手動(dòng)“開啟,我們來看下相關(guān)源碼:
我們無法直接創(chuàng)建子線程的runloop伐蒋,而是通過GetCurrent獲取當(dāng)前runloop工三,底層會(huì)判斷是否存在,如果沒有則創(chuàng)建runloop先鱼,并放入table里徒蟆。下次直接從table里返回runloop。
我們?cè)倏聪翹SRunloop的對(duì)象型型,它實(shí)際是一個(gè)cfrunloop的結(jié)構(gòu)體段审,里面有鎖、Modes、ModeItems等寺枉。
這里再引入RunLoop主要涉及的五個(gè)類:
CFRunLoop:RunLoop對(duì)象抑淫、
CFRunLoopMode:五種RunLoop運(yùn)行模式、
CFRunLoopSource:輸入源/事件源姥闪,包括Source0?和?Source1
CFRunLoopTimer:定時(shí)源始苇,就是NSTimer、
CFRunLoopObserver:觀察者筐喳,用來監(jiān)聽RunLoop催式。
CFRunLoopMode:RunLoop運(yùn)行模式,有五種:
①kCFRunLoopDefaultMode:默認(rèn)的運(yùn)行模式避归,通常主線程是在這個(gè) Mode 下運(yùn)行的荣月。
②UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng)梳毙,保證界面滑動(dòng)時(shí)不受其他 Mode 影響哺窄。
③UIInitializationRunLoopMode:在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用账锹。
④GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode萌业,通常用不到。
⑤kCFRunLoopCommonModes:是一個(gè)偽模式奸柬,可以在標(biāo)記為CommonModes的模式下運(yùn)行生年,RunLoop會(huì)自動(dòng)將_commonModeItems里的?Source、Observer廓奕、Timer?同步到具有此標(biāo)記的Mode里抱婉。
CFRunLoopSource:輸入源/事件源,包括Source0 和 Source1兩種:
Source1:基于mach_Port懂从,處理來自系統(tǒng)內(nèi)核或其它進(jìn)程的事件授段,比如點(diǎn)擊手機(jī)屏幕蹲蒲。
Source0?:非基于Port的處理事件番甩,也就是應(yīng)用層事件,需要手動(dòng)標(biāo)記為待處理和手動(dòng)喚醒RunLoop届搁。
簡(jiǎn)單舉例:一個(gè)APP在前臺(tái)靜止缘薛,用戶點(diǎn)擊APP界面,屏幕表面的事件會(huì)先包裝成Event告訴source1(mach_port)卡睦,source1喚醒RunLoop將事件Event分發(fā)給source0宴胧,由source0來處理。
CFRunLoopTimer:定時(shí)源表锻,就是NSTimer恕齐。在預(yù)設(shè)的時(shí)間點(diǎn)喚醒RunLoop執(zhí)行回調(diào)。因?yàn)樗腔赗unLoop的瞬逊,因此它不是實(shí)時(shí)的(就是NSTimer 是不準(zhǔn)確的显歧。 因?yàn)镽unLoop只負(fù)責(zé)分發(fā)源的消息仪或。如果線程當(dāng)前正在處理繁重的任務(wù),就有可能導(dǎo)致Timer本次延時(shí)士骤,或者少執(zhí)行一次)范删。
CFRunLoopObserver:觀察者,用來監(jiān)聽以下時(shí)間點(diǎn):CFRunLoopActivity
kCFRunLoopEntry:RunLoop準(zhǔn)備啟動(dòng)
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)
從runloop結(jié)構(gòu)體可以看出到旦,runloop對(duì)象里可以有多個(gè)mode,實(shí)際上mode和source巨缘、timer添忘、observer也是一對(duì)多的關(guān)系〈铮可以理解為一個(gè)線程只能有一個(gè)runloop昔汉,一個(gè)runloop可以有多種mode來處理各種不同的事務(wù),每個(gè)mode下可以有多個(gè)source和timer拴清。
我們?cè)谧泳€程獲取到runloop對(duì)象后靶病,必須要讓它run起來,這樣才能使runloop的笨谟瑁活功能生效(當(dāng)然這是通過驗(yàn)證而得)娄周。下面我們來看下run:
runloop在run的時(shí)候,底層實(shí)際是一個(gè)do while循環(huán)沪停,這個(gè)循環(huán)不是一直在運(yùn)行煤辨,而是通過端口集mach port set來控制循環(huán),如果不需要處理事情的時(shí)候木张,就進(jìn)行休眠狀態(tài)众辨,休眠后需要通過wakeup來喚醒,休眠時(shí)線程被block住舷礼,此時(shí)需要在另外一個(gè)線程對(duì)它進(jìn)行喚醒鹃彻。
從上圖可知,調(diào)用CFRunLoopWakeUp的時(shí)候妻献,實(shí)際是通過mach port來發(fā)送MACH_SEND_MSG的權(quán)限
從上圖可知蛛株,喚醒runloop有四種方式:
1、Timer事件(底層實(shí)際也是通過端口port來實(shí)現(xiàn)的)
2育拨、Source 1(也是基于端口來實(shí)現(xiàn)的)
3谨履、手動(dòng)wake up(上面提過)
4、處于超時(shí)狀態(tài)(可以手動(dòng)設(shè)置超時(shí)時(shí)間熬丧,當(dāng)超時(shí)后笋粟,會(huì)先喚醒runloop,然后再進(jìn)行退出)
以下是Runloop源碼,可以結(jié)合上面的循環(huán)機(jī)制加以理解:
第20行__CFRunLoopRun函數(shù)是核心害捕,進(jìn)入前后會(huì)通知觀察者唆香,__CFRunLoopRun內(nèi)部是一個(gè)do-while循環(huán)。
舉例:第77行__CFRunLoopDoTimers實(shí)際就會(huì)調(diào)用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__吨艇,然后執(zhí)行timer躬它。通過查看調(diào)用棧也可以得到。
那這個(gè)Timer是如何添加進(jìn)runloop的呢东涡?
通過上面對(duì)CommonMode的了解冯吓,RunLoop會(huì)自動(dòng)將_commonModeItems里的?Source、Observer疮跑、Timer?同步到具有此標(biāo)記的CommonMode里组贺。
在執(zhí)行- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode函數(shù)后,實(shí)際就是將timer添加到相應(yīng)的mode里去祖娘,這里是[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
添加到CommonModes中后失尖,在runloop開始run的時(shí)候,就可以通過__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__通知timer去執(zhí)行渐苏。然后通過判斷retVal的值掀潮,看runloop是否需要結(jié)束,或是讓runloop休眠琼富。至此就完成了一輪”圈“仪吧。
補(bǔ)充:CommonMode并不是一種實(shí)際的模式,和defaultmode完全不是一回事鞠眉。簡(jiǎn)單的理解就是薯鼠,如果設(shè)置了?CommonMode模式,那么runloop在切換mode的同時(shí)械蹋,也把timer的事件也帶走了出皇,所以無論切到哪種mode下,timer的事件都是可以處理的哗戈。
回答tableView滾動(dòng)timer不走問題:
1.默認(rèn)滾動(dòng)事件和timer事件都是在kCFRunloopDefaultMode下的
2.此時(shí)tableView一滾動(dòng)郊艘,mode切換到UITrackingRunloopMode中,上面說谱醇,runloop同一時(shí)間只能處理一種mode的事件暇仲,那么在DefaultMode的timer就無法響應(yīng)了步做。
3.CommonMode又是一個(gè)同步多種mode的技術(shù)方案副渴,此模式下,當(dāng)runloop到UITrackingRunloopMode的時(shí)候全度,他把timer的事件也轉(zhuǎn)移到UITrackingRunloopMode下煮剧,那么timer就能走了。
Runloop的應(yīng)用1:
點(diǎn)擊事件通過硬件(屏幕)夸進(jìn)程轉(zhuǎn)發(fā)給SpringBoard(桌面管理),再通過mach port夸進(jìn)程轉(zhuǎn)發(fā)給當(dāng)前app勉盅,app再通過Source1和Source0回調(diào)佑颇,最后傳給app的UIWindow。
Runloop的應(yīng)用2:
檢測(cè)主線程卡頓