前言
相信大家對Runloop都或多或少有過一定的了解,就算沒有使用過Runloop但也應(yīng)該聽說,尤其對于iOS開發(fā)。這篇文章并不會(huì)詳細(xì)講解Runloop的內(nèi)部實(shí)現(xiàn)與工作原理,主要以iOS開發(fā)的角度介紹Runloop的使用實(shí)踐。
什么是Runloop
Runloop簡單來說就是一個(gè)消息循環(huán),如同do{}While,主用用于資源的合理分配,有消息時(shí)喚起Runloop處理消息、沒有消息時(shí)進(jìn)入休眠、合適時(shí)機(jī)切換循環(huán)的mode,避免消息堆積。網(wǎng)上介紹Runloop的文章很多也很詳細(xì),并且蘋果已經(jīng)開源了CoreFoundation源代碼以及Runloop官方文檔,這里也不展開介紹了。
實(shí)踐
1.卡頓監(jiān)控
卡頓在用戶的感知是頁面掉幀、無法操作、操作響應(yīng)不及時(shí),在研發(fā)的角度來看就是主線程正在執(zhí)行耗時(shí)代碼,導(dǎo)致頁面渲染頻率跟不上CPU刷新頻率或主線程無法及時(shí)響應(yīng)用戶交互事件。
- 通用解決方案
使用CADisplayLink計(jì)算FPS,通過FPS監(jiān)控卡頓情況,但是這種方案局限較大,無法精確定位導(dǎo)致卡頓的問題代碼。 - 卡頓的根本原因位
用戶交互事件的響應(yīng)與頁面渲染幀的計(jì)算在每個(gè)消息循環(huán)內(nèi)都能處理,故如果單個(gè)消息循環(huán)耗時(shí)不長,就不會(huì)導(dǎo)致卡頓的產(chǎn)生 - Runloop方案
在子線程通過CFRunLoopObserver監(jiān)聽主線程Runloop的CFRunLoopActivity狀態(tài),根據(jù)kCFRunLoopBeforeSources與kCFRunLoopAfterWaiting兩個(gè)狀態(tài)確定每個(gè)消息循環(huán)耗時(shí)情況,根據(jù)合理的閥值找出有問題的消息循環(huán),并通過當(dāng)時(shí)的函數(shù)調(diào)用堆棧即可獲取問題代碼,可用于統(tǒng)計(jì)卡頓的量與問題分析。
2.預(yù)加載分發(fā)
性能優(yōu)化過程中少不了預(yù)加載、預(yù)計(jì)算等手段,這些手段確實(shí)能給某些指標(biāo)帶來可觀的收益,但是預(yù)加載的時(shí)機(jī)也是個(gè)需要考慮的問題,如果時(shí)機(jī)沒選好或者多個(gè)預(yù)加載堆積可能會(huì)更影響體驗(yàn)。
- 案例
如TableView的性能優(yōu)化的過程中,我們會(huì)預(yù)計(jì)算為展示cell的布局或cell高度,以保證滑動(dòng)的流程性。常規(guī)做法是在TableView滑停的時(shí)候進(jìn)行預(yù)計(jì)算,但一般用戶滑停后大概率會(huì)進(jìn)行點(diǎn)擊等操作,而預(yù)計(jì)算可能導(dǎo)致用戶無法及時(shí)交互。 - 空閑分發(fā)
建立一個(gè)預(yù)加載分發(fā)隊(duì)列,同時(shí)監(jiān)聽主線程Runloop的kCFRunLoopBeforeWaiting狀態(tài),每監(jiān)聽到一次變?yōu)?code>BeforeWaiting的時(shí)機(jī),就將一個(gè)預(yù)加載任務(wù)通過performSelector:onThread:withObject:waitUntilDone:modes:以source0的方式拋給主線程Runloop處理。同時(shí)隊(duì)列可提供任務(wù)的優(yōu)先級排序與任務(wù)的撤銷,便于使用方的資源合理分配與預(yù)加載回收。
3.容災(zāi)?;?/h4>
App出現(xiàn)Crash發(fā)生異常奔潰是在所難免的事,雖然我們可以通過加保護(hù)、消息轉(zhuǎn)發(fā)等手段做到一定程度的容災(zāi),但還是無法預(yù)測所有的異常發(fā)生,在異常發(fā)生后希望給用戶一定的提示或者短暫的操作時(shí)間,進(jìn)行數(shù)據(jù)的保持等行為。
- 異常捕獲
系統(tǒng)提供了NSSetUncaughtExceptionHandler()與signal()用于捕獲native與signal的異常處理,但受限于只有當(dāng)前ExceptionHandler所在Runloop的執(zhí)行時(shí)間,期間無法給用戶提供操作時(shí)間。 - ?;?br>
在
ExceptionHandler執(zhí)行過程中可通過CFRunLoopRun()或CFRunLoopRunInMode()重啟主線程Runloop,臨時(shí)掛起ExceptionHandler的上下文。期間提示用戶且用戶可正常操作,待操作完成后以CFRunLoopStop()關(guān)閉ExceptionHandler中新起的消息循環(huán)并還原ExceptionHandler的上下文,完成異常處理的完整流程。 - 風(fēng)險(xiǎn)
-
ExceptionHandler的執(zhí)行時(shí)間是否受Watch Dog的監(jiān)控未知,暫未查到資料。 - 異常已經(jīng)觸發(fā),臨時(shí)?;钜部赡苁遣煌暾δ?。
- 保活后系統(tǒng)主線程調(diào)用堆棧都是從
ExceptionHandler開始的,即堆棧中ExceptionHandler變成正常情況下main函數(shù)的位置。
-
小結(jié)
以上幾種Runloop的落地方案均只提及思想,未提供具體實(shí)現(xiàn)細(xì)節(jié),主要是因各個(gè)場景下有各自的訴求,即實(shí)現(xiàn)細(xì)節(jié)各有千秋??D監(jiān)控與預(yù)加載分發(fā)方案落地沒有什么異議,至于容災(zāi)?;钍欠窨陕涞乜勺们檫x擇。再者大家有其他更好的Runloop落地方案也歡迎留言補(bǔ)充。