性能优化02.1:FPS监测

张建 lol

监控 FPS

  • 一般来说,我们约定 60FPS 即为流畅,那么反过来,如果App在运行期间出现了掉帧,即可认为出现了卡顿。

  • 监控FPS方案一般是基于 CADisplayLink 实现的。CADisplayLink 是一个和屏幕刷新率保持一致的定时器,一旦 CADisplayLink 以特定的模式注册到 runloop 后,没当屏幕需要刷新时,runloop 就会调用 CADisplayLink 绑定的 target 上的 selector

  • 通过向 runloop 中添加 CADisplayLink,根据回调来计算出当前画面的 帧数

  • FPS好处 就是直观,根据 FPS 是否下降说明页面某处是否有性能问题。

  • FPS坏处 就是只知道页面的某处,不能准确定位到具体的 堆栈

具体实现

  • 创建一个 ZJFPSMonitor 工具类,实现如下代码:

.h 文件下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZJFPSMonitor : NSObject
// 返回屏幕刷新率FPS
@property (nonatomic,copy)void(^FPSMonitorBlock)(NSInteger fps);
// 单例
+ (instancetype)shareInstance;
// 开始监测
- (void)startMonitor;
@end

NS_ASSUME_NONNULL_END

.m 文件下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#import "ZJFPSMonitor.h"
#import <UIKit/UIKit.h>
#import <ZJWeakProxy/ZJWeakProxy.h>

@interface ZJFPSMonitor ()
@property (nonatomic,strong)CADisplayLink * displayLink;
@property (nonatomic,assign)NSTimeInterval lastUpdateTime; // 上一次更新时间
@property (nonatomic,assign)NSInteger count; // 刷新次数
@property (nonatomic,assign)NSInteger fps; // FPS
@end
@implementation ZJFPSMonitor
// 单例
+ (instancetype)shareInstance{
static ZJFPSMonitor * instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[ZJFPSMonitor alloc] init];
});
return instance;
}

#pragma mark -开始监测
- (void)startMonitor{
if (_displayLink != nil) {
return;
}
// 线程保活
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

#pragma mark -计算FPS
- (void)fpsInfoAction:(CADisplayLink *)displayLink{
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = displayLink.timestamp;
}
// 刷新次数
self.count ++;
// 时间间隔
NSTimeInterval interval = displayLink.timestamp - self.lastUpdateTime;
// 如果满足更新FPS时间间隔
if (interval >= 1) {
// 赋值fps
self.fps = self.count / interval;
// 赋值上次更新时间
self.lastUpdateTime = displayLink.timestamp;
// 重置数量
self.count = 0;
// 返回FPS
if (self.FPSMonitorBlock) {
self.FPSMonitorBlock(self.fps);
}
}
}

#pragma mark -lazy
- (CADisplayLink *)displayLink{
if (!_displayLink) {
_displayLink = [CADisplayLink displayLinkWithTarget:[ZJWeakProxy proxyWithTarget:self] selector:@selector(fpsInfoAction:)];
// 屏幕刷新率
_displayLink.preferredFramesPerSecond = 60;
}
return _displayLink;
}

@end

  • VC 中调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import "ViewController.h"
#import "ZJFPSMonitor.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// 开始监测FPS
[ZJFPSMonitor.shareInstance startMonitor];
ZJFPSMonitor.shareInstance.FPSMonitorBlock = ^(NSInteger fps) {
NSLog(@"%ld",fps);
};

}
@end

查看打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
2023-03-23 06:58:00.945820+0800 FPS[27330:1519462] 60
2023-03-23 06:58:01.962348+0800 FPS[27330:1519462] 60
2023-03-23 06:58:02.979022+0800 FPS[27330:1519462] 60
2023-03-23 06:58:03.995646+0800 FPS[27330:1519462] 60
2023-03-23 06:58:05.012252+0800 FPS[27330:1519462] 60
2023-03-23 06:58:06.028901+0800 FPS[27330:1519462] 60
2023-03-23 06:58:07.045433+0800 FPS[27330:1519462] 60
2023-03-23 06:58:08.062067+0800 FPS[27330:1519462] 60
2023-03-23 06:58:09.078645+0800 FPS[27330:1519462] 60
2023-03-23 06:58:10.095264+0800 FPS[27330:1519462] 60
2023-03-23 06:58:11.111855+0800 FPS[27330:1519462] 60
2023-03-23 06:58:12.128443+0800 FPS[27330:1519462] 60
  • Post title:性能优化02.1:FPS监测
  • Post author:张建
  • Create time:2023-03-23 06:53:11
  • Post link:https://redefine.ohevan.com/2023/03/23/OC性能优化/性能优化02.1:FPS监测/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
On this page
性能优化02.1:FPS监测