在開(kāi)發(fā)中,我們可以使用Xcode自帶的Instruments工具的Core Animation來(lái)對(duì)APP運(yùn)行流暢度進(jìn)行監(jiān)控,使用FPS這個(gè)值來(lái)衡量。這個(gè)工具我們只能知道哪個(gè)界面會(huì)有卡頓,無(wú)法知道到底是什么操作哪個(gè)函數(shù)導(dǎo)致的卡頓。

界面出現(xiàn)卡頓,一般是下面幾種原因:
- 主線(xiàn)程做大量計(jì)算
- 主線(xiàn)程大量的I/O操作
- 大量的UI繪制
- 主線(xiàn)程進(jìn)行網(wǎng)絡(luò)請(qǐng)求以及數(shù)據(jù)處理
- 離屏渲染
監(jiān)控界面卡頓,主要是監(jiān)控主線(xiàn)程做了哪些耗時(shí)的操作,之前的文章中已經(jīng)分析過(guò),iOS中線(xiàn)程的事件處理依靠的是RunLoop,正常FPS值為60,如果單次RunLoop運(yùn)行循環(huán)的事件超過(guò)16ms,就會(huì)使得FPS值低于60,如果耗時(shí)更多,就會(huì)有明顯的卡頓。
正常RunLoop運(yùn)行循環(huán)一次的流程是這樣的:
SetupThisRunLoopRunTimeOutTimer();
do {
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks();
__CFRunLoopDoSource0(); // 處理source0事件,UIEvent事件,比如觸屏點(diǎn)擊
CheckIfExitMessagesInMainDispatchQueue(); // 檢查是否有分配到主隊(duì)列中的任務(wù)
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
var wakeUpPort = SleepAndWaitForWakingUpPorts(); // 開(kāi)始休眠,等待ma ch_msg事件
// mach_msg_trap
// ZZz..... sleep
// Received mach_msg, wake up
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting); // 被事件喚醒
// Handle msgs
if (wakeUpPort == timePort) { // 被喚醒的事件是timer
__CFRunLoopDoTimers();
} else if (wakePort == mainDispatchQueuePort) { // 主隊(duì)列有調(diào)度任務(wù)
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
} else { // source1事件,UI刷新,動(dòng)畫(huà)顯示
__CFRunLoopDoSource1();
}
__CFRunLoopDoBlocks();
} while (!stop && !timeout)
從這個(gè)運(yùn)行循環(huán)中可以看出,RunLoop休眠的事件是無(wú)法衡量的,處理事件的部分主要是在kCFRunLoopBeforeSources之后到kCFRunLoopBeforeWaiting之前和kCFRunLoopAfterWaiting 之后和運(yùn)行循環(huán)結(jié)束之前這兩個(gè)部分
監(jiān)控這兩個(gè)部分的耗時(shí),使用CFRunLoopObserverRef來(lái)監(jiān)控RunLoop的狀態(tài):

使用信號(hào)量dispatch_semaphore來(lái)控制對(duì)RunLoop狀態(tài)判斷的節(jié)奏,這個(gè)可以保證,每個(gè)RunLoop狀態(tài)的判斷都會(huì)進(jìn)行。
對(duì)RunLoop狀態(tài)的判斷,我們專(zhuān)門(mén)在另外一個(gè)線(xiàn)程做判斷。
需要注意的是,對(duì)卡頓的判斷是通過(guò)
kCFRunLoopBeforeSources或者kCFRunLoopBeforeWaiting這兩個(gè)狀態(tài)開(kāi)始后,信號(hào)量+1,這時(shí)候信號(hào)量>0,dispatch_semaphore_wait不會(huì)阻塞,返回0,進(jìn)行下一個(gè)while循環(huán),如果此時(shí)還沒(méi)有進(jìn)入下一個(gè)RunLoop狀態(tài),此時(shí)信號(hào)量=0,dispatch_semaphore_wait就會(huì)在這里阻塞,到了設(shè)定的超時(shí)時(shí)間,dispatch_semaphore_wait的返回值>0,這時(shí)候就會(huì)進(jìn)行耗時(shí)的判斷。
我們可以自己設(shè)定超時(shí)時(shí)間和超過(guò)多少次算卡頓,這里設(shè)置超過(guò)250ms。
