iOS FPS監測

kim_jin發表於2018-01-09

現在如果在網路上搜的話,基本上大多數用於檢測FPS的控制元件都是通過CADisplayLink來實現的。

CADisplayLink

官方文件對於CADisplayLink的介紹是:

A timer object that allows your application to synchronize its drawing to the refresh rate of the display.

即與螢幕重新整理率同步的時間物件。

一般情況下,我們的螢幕重新整理率是1/60s一次。CADisplayLink實際上跟平常用的NSTimer的用法基本相似,NSTimer的時間間隔是以秒為單位,而CADisplayLink則是使用幀率來作為時間間隔的單位。

利用CADisplayLink來實現FPS監測的常規做法如下:

var historyCount: Int = 0
var lastUpdateTimestamp: Double = 0
let displayLink = CADisplayLink(target: self, selector: #selector(step(displayLink:))

// ...

func step(displayLink: CADisplayLink) {
    if (lastUpdateTimestamp <= 0) {
        lastUpdateTimestamp = displayLink.timestamp
    }
    
    historyCount += 1
    let interval = displayLink.timestamp - lastUpdateTimestamp
    if (interval >= 1) {
        lastUpdateTimestamp = 0
        let fps = Double(historyCount) / interval
        print(fps)
        historyCount = 0
    }
}
複製程式碼

核心思想為:在初始化CADisplayLink物件時,指定方法,該方法會在每次螢幕重新整理,即每1/60秒呼叫一次,通過計算方法的呼叫次數以及時間間隔,來獲取當前螢幕的fps

測試

根據上面的程式碼,我建立了一個tableView,在cell中各種圓角圖片,反正就是怎麼卡怎麼來:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
    cell!.imageView!.image = UIImage(named: "00" + String(indexPath.row % 8 + 1))
    cell!.imageView!.layer.cornerRadius = 10
    cell!.imageView!.clipsToBounds = true
    cell!.imageView!.layer.shadowOffset = CGSize(width: 0, height: 5)
    cell!.imageView!.layer.shadowOpacity = 1

    if (indexPath.row % 3 == 0) {
        cell!.textLabel!.text = "上路 鞏州遇虎熊五百年前一場瘋騰霄又是孫悟空失馬 鷹愁澗飛白龍沙河阻斷 路難通福陵山中收天"
    } else if (indexPath.row % 3 == 1) {
        cell!.textLabel!.text = "嶺上 前行逆黃風七星不照 波月洞千年白骨 化陰風魚籃 網通天一尾紅紫金葫蘆二道童九尾老狐敢壓龍白虹墜 雪浪擊石碎思歸 難歸 墮回 輪迴"
    } else {
        cell!.textLabel!.text = "紅霓垂 九重紫雲飛久歸 未歸 欲回 恨回凡胎恰登對 天命難違比丘走白鹿 十三娘情絲纏縛烏袍君生百目廟前攔路自稱黃眉老祖"
    }
 
    cell!.textLabel!.backgroundColor = UIColor.clear
    cell!.textLabel!.layer.shadowOffset = CGSize(width: 0, height: 2)
    cell!.textLabel!.layer.shadowOpacity = 1
    cell!.textLabel!.numberOfLines = 0

    return cell!
}
複製程式碼

在執行時可以看到,列印出來的幀率為:

CADisplayLink 幀率

可是通過Instrument的Core Animation進行監測的時候,其結果卻是:

Instrument 結果

兩者完全就對不上啊。

在這篇文章中,發現作者也遇到相同的問題:iOS中基於CADisplayLink的FPS指示器詳解

根據大神ibireme的文章iOS 保持介面流暢的技巧的介紹,我們能夠知道在螢幕中顯示影象的過程中,CPU負責計算顯示內容,進行諸如檢視建立,佈局計算,圖片解碼等工作,然後將資料提交到GPU上,而GPU對這些影象資料進行變換,渲染之後,會把影象提交到幀緩衝區,然後在下一次同步訊號來臨的時候,將影象顯示到螢幕上。然後GPU就切換指向到另一個幀緩衝區,重複上述工作。

由此可以得知,因為CADisplayLink的執行取決於RunLoop。而RunLoop的執行取決於其所在的mode以及CPU的繁忙程度,當CPU忙於計算顯示內容或者GPU工作太繁重時,就會導致顯示出來的FPS與Instrument的不一致。

故使用CADisplayLink並不能很準確反映當前螢幕的FPS!

主執行緒卡頓監測

由於CADisplayLink並不能夠準確反映出來,所以常用的方法時主執行緒卡頓監測。通過開闢一個子執行緒來監測主執行緒的RunLoop,當兩個狀態區域的耗時大於設定的閾值時,即為一次卡頓。

根據如何監控卡頓的介紹,可以得知主執行緒卡頓監測的原理以及做法。

結論

根據CADisplayLink寫了一個小工具:KJFPSLabel

相關文章