iOS 如何监测 FPS
前言
现在如果在网络上搜的话,基本上大多数用于检测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!
}
在运行时可以看到,打印出来的帧率为:
可是通过 Instrument
的 Core Animation
进行监测的时候,其结果却是:
两者完全就对不上啊。
在这篇文章中,发现作者也遇到相同的问题:iOS中基于CADisplayLink的FPS指示器详解[1]
根据大神 ibireme 的文章iOS 保持界面流畅的技巧[2]的介绍,我们能够知道在屏幕中显示图像的过程中,CPU 负责计算显示内容,进行诸如视图创建,布局计算,图片解码等工作,然后将数据提交到 GPU 上,而 GPU 对这些图像数据进行变换,渲染之后,会把图像提交到帧缓冲区,然后在下一次同步信号来临的时候,将图像显示到屏幕上。然后 GPU 就切换指向到另一个帧缓冲区,重复上述工作。
由此可以得知,因为 CADisplayLink
的运行取决于 RunLoop
。而 RunLoop
的运行取决于其所在的 mode
以及 CPU 的繁忙程度,当 CPU 忙于计算显示内容或者 GPU 工作太繁重时,就会导致显示出来的 FPS 与 Instrument 的不一致。
故使用 CADisplayLink
并不能很准确反映当前屏幕的 FPS!
主线程卡顿监测
由于 CADisplayLink
并不能够准确反映出来,所以常用的方法时主线程卡顿监测。通过开辟一个子线程来监测主线程的 RunLoop,当两个状态区域的耗时大于设定的阈值时,即为一次卡顿。
根据如何监控卡顿[3]的介绍,可以得知主线程卡顿监测的原理以及做法。
参考资料
[1] iOS中基于CADisplayLink的FPS指示器详解: https://www.jianshu.com/p/86705c95c224
[2] iOS 保持界面流畅的技巧: https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
[3] 如何监控卡顿: https://github.com/aozhimin/iOS-Monitor-Platform
相关文章
- GitHub 近两万 Star,无需编码,可一键生成前后端代码 上
- GitHub 近两万 Star,无需编码,可一键生成前后端代码 下
- 【每日一题】力扣剑指 Offer II 075. 数组相对排序
- 【随笔】数组元素使用异或交换位置的算法引发的思考
- Linux与Windows的UDP通讯代码实现
- Go --- for range会使通道中的缓存值被取出
- Go ---Go语言高级编程中订阅/发布模型例子解析
- 你也可以是小hacker ,Google Hacking,好可怕的搜索引擎!
- Go -- 方法中何时使用值传递何时使用指针共享
- Go --- continue标签跳转
- 谷粒学苑项目实战(二):讲师管理模块搭建(下)
- 谷粒学苑项目实战(三):整合Swagger2
- 谷粒学苑项目实战(四):将返回结果统一为json格式
- 图解 Google V8 # 16:V8是怎么通过内联缓存来提升函数执行效率的?
- 谷粒学苑项目实战(五):统一异常处理
- 谷粒学苑项目实战(六):统一日志处理
- 谷粒学苑项目实战(七):搭建前端页面
- 杨洋撒撒一大片,Controller接收中文不再“不正经”,乱码问题这样解决,你信或不信
- 图解 Google V8 # 17:消息队列:V8是怎么实现回调函数的?
- KONGSBERG RMP420 康士伯 海上船舶控制系统模块