繼上一篇博客
中高級(jí)iOS必備知識(shí)點(diǎn)之RunLoop(一)繼續(xù)介紹
RunLoop的狀態(tài)
首先我們?nèi)unLoop的源碼去查看它有幾種狀態(tài),如下圖:
它一共有上面的這幾種個(gè)狀態(tài)
/* Run Loop Observer Activities */
typedef?CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入loop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理timer
kCFRunLoopBeforeSources = (1UL << 2), //即將處理source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), //即將退出loop
kCFRunLoopAllActivities = 0x0FFFFFFFU //所有模式
};
現(xiàn)在我們來(lái)試一試,怎么去監(jiān)聽(tīng)RunLoop的狀態(tài),是這樣,RunLoop的監(jiān)聽(tīng)模式?jīng)]有OC的代碼,我們可以用C語(yǔ)言代碼來(lái)實(shí)現(xiàn),如下:
從執(zhí)行的結(jié)果來(lái)看,確實(shí)是這么多,點(diǎn)擊事件是在source0執(zhí)行,所以看log日志也是很清楚,
接下來(lái)我們看一下定時(shí)器喚醒RunLoop.我們知道kCFRunLoopBeforeWaiting是休眠睡覺(jué),而kCFRunLoopAfterWaiting是喚醒休眠,我們看一下定時(shí)器是不是喚醒休眠,請(qǐng)看下面的代碼:
從輸出結(jié)果來(lái)看,確實(shí)是喚醒休眠.執(zhí)行block
證明模式切換會(huì)退出RunLoop,再重新進(jìn)入RunLoop
由上面的監(jiān)聽(tīng),我們是很容易可以證明這個(gè)結(jié)果吧?我們看一下代碼,這次用上面說(shuō)的另一種創(chuàng)建observe的方法,請(qǐng)看下圖:隨便創(chuàng)建一個(gè)可以滾動(dòng)的view,比如我創(chuàng)建的Scrollerview.
當(dāng)在滾動(dòng)的時(shí)候,我們明顯可以看到2種模式在切換,而且RunLoop也是需要退出重新進(jìn)入才會(huì)切換到新的模式.
深入理解RunLoop的執(zhí)行流程
網(wǎng)上的答案很多,這次我們從源碼解析,一步一步的查看RunLoop的執(zhí)行流程到底是怎么樣的:因?yàn)樵创a比較抽象,是純c語(yǔ)言的,不像我們之前的有c++源碼比較好懂一點(diǎn),那我們?cè)趺凑襯unloop開(kāi)始的函數(shù)呢?很容易,我們知道點(diǎn)擊也是通過(guò)runloop來(lái)處理的,那我們直接看點(diǎn)擊事件的函數(shù)調(diào)用棧就知道入口了,請(qǐng)看下圖:
從上面的代碼可以很清楚的看出來(lái)是調(diào)用了CFRunLoopRunSpecific這個(gè)函數(shù).那我們就去源碼搜索這段代碼很容易就搜索到.請(qǐng)看下面:
一看上面的源碼,我們發(fā)現(xiàn)執(zhí)行了非常多,你看綠色里面有鎖,有多線程等等,所以我們沒(méi)有必要研究得很透徹,浪費(fèi)時(shí)間也沒(méi)有意義,我們只要把大致流程捋順了就行了,所以我們只要看關(guān)鍵代碼,我只展示我們要看的關(guān)鍵代碼如下:
接下來(lái)我們就去看__CFRunLoopRun源碼里面到底執(zhí)行了哪些操作,接下來(lái)的源碼是我精簡(jiǎn)了的,大家可以參照源碼看一下,因?yàn)槔锩鏂|西非常多,我們沒(méi)有必要全部了解,只要知道它的執(zhí)行流程即可.請(qǐng)看下圖:
如果條件不成立,就會(huì)設(shè)置返回值,到時(shí)候直接退出RunLoop,上面的流程相信我備注的已經(jīng)非常清楚,對(duì)照一下源碼看一下,你會(huì)印象更加深刻.下面我們用文字總結(jié)一下流程如下:
再放一個(gè)文字版的:
這個(gè)和源碼基本一模一樣的流程,可以參照一下,源碼還是比較抽象的
接下來(lái)再看一個(gè)知識(shí)點(diǎn).
__CFRunLoopDoBlocks、__CFRunLoopDoObservers、__CFRunLoopDoTimers里面執(zhí)行了什么
其中RunLoop做了這么幾件大事中比如blocks,timers,observers,我們看下里面具體是執(zhí)行了什么,繼續(xù)看源碼,請(qǐng)看下圖,比如我們就看__CFRunLoopDoSources0:
其實(shí)最終處理的就是這個(gè):
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
我們?cè)倏聪轮暗囊粋€(gè)截圖
它上面兩個(gè)處理的函數(shù)是一摸一樣,也進(jìn)一步的證明了,觸摸事件是source0在處理.
定時(shí)器也是一樣的道理,大家可以自己嘗試一下.
理解線程休眠具體是什么意思?
RunLoop的線程休眠是真的休眠,它是不會(huì)占用任何cpu的資源,完全休息.它和white(1)這種還是有本質(zhì)的區(qū)別,white(1)它是一直在執(zhí)行,轉(zhuǎn)成匯編會(huì)有幾條指令,一直在執(zhí)行,一直占用cpu資源,比如我們想做優(yōu)化,那RunLoop的這種休眠模式是不是更節(jié)約cpu資源,說(shuō)白了更省電些.就是如下源碼的位置:
就是執(zhí)行到當(dāng)前代碼,就不會(huì)往下走了,不會(huì)接著執(zhí)行下面的代碼,就會(huì)堵在這里,一旦別人喚醒它了,它才會(huì)接著往下執(zhí)行.我們可能奇怪它是怎么做到的這種休眠?
我們看一下里面具體是怎么實(shí)現(xiàn)的,其實(shí)里面是執(zhí)行了非常內(nèi)核的函數(shù)叫做:mach_msg,我們可以看下
其實(shí)IPA可以分為內(nèi)核層面的IPA:它是非常非常底層的,是操作系統(tǒng)層面的,它可以讓線程休眠,也是真的休眠,不占用任何cpu資源,一般是不開(kāi)放給我們程序員使用的,因?yàn)槭潜容^內(nèi)核的,給程序員使用危險(xiǎn)也比較大,而應(yīng)用層面的IPA,都是網(wǎng)絡(luò)請(qǐng)求什么,頁(yè)面什么.
所以mach_msg,我們面試的時(shí)候可以答出這個(gè)函數(shù).
RunLoop休眠實(shí)現(xiàn)的原理
就是用戶態(tài)和內(nèi)核態(tài)的切換,用戶態(tài)發(fā)消息,內(nèi)核態(tài)休眠,再被喚醒,用戶態(tài)處理消息.
所以如果面試官問(wèn):RunLoop里面線程阻塞是怎么樣的?我們千萬(wàn)不能答,里面是個(gè)死循環(huán).就是上面剛剛說(shuō)的那些.
RunLoop與NSTimer的故事
相信我們?cè)陂_(kāi)發(fā)中遇到的次數(shù)是非常的多,這里稍微提一下.
比如我們常見(jiàn)的mode模式是有2種
1.KCFRunLoopDefaultMode (NSDefaultRunLoopMode):App的默認(rèn)Mode,通常是主線程是在這個(gè)Mode下運(yùn)行
2.UITrackingRunLoopMode : 界面跟蹤Mode,用于ScrollView追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode影響
我們?cè)诎裈imer添加到runloop的時(shí)候,直接傳入通用模式即可NSRunLoopCommonModes即可.
主要說(shuō)一下它是怎么個(gè)原因.
我們看一下上個(gè)博客說(shuō)的RunLoop的結(jié)構(gòu):
我們可以理解為傳入NSRunLoopCommonModes,就是把這2種模式,放入_commonModes里面,也就是timer可以_commonModes數(shù)組中的模式下進(jìn)行工作.注意NSRunLoopCommonModes這個(gè)不是一種模式哈.
而_commonModeItems里面存放的都是可以在_commonModes模式下工作的,比如剛剛的timer.