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