基于 CADisplayLink 的 FPS 指示器详解

发布时间:2017-5-28 0:07:02 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"基于 CADisplayLink 的 FPS 指示器详解 ",主要涉及到基于 CADisplayLink 的 FPS 指示器详解 方面的内容,对于基于 CADisplayLink 的 FPS 指示器详解 感兴趣的同学可以参考一下。

前言

之前在开发中有使用到计时器NSTimer,后来了解到iOS中不同的计时方法,其中就包括了CADisplayLink。基于CADisplayLink以屏幕刷新频率同步绘图的特性,尝试根据这点去实现一个可以观察屏幕当前帧数的指示器。

结论在前

根据CADisplayLink所实现的FPS指示器在实际生产场景下只有指导意义,不能代表真实的FPS,具体原因见下文。

什么是CADisplayLink

CADisplayLinkCoreAnimation提供的另一个类似于NSTimer的类,它总是在屏幕完成一次更新之前启动,它的接口设计的和NSTimer很类似,所以它实际上就是一个内置实现的替代,但是和timeInterval以秒为单位不同,CADisplayLink有一个整型的frameInterval属性,指定了间隔多少帧之后才执行。默认值是1,意味着每次屏幕更新之前都会执行一次。但是如果动画的代码执行起来超过了六十分之一秒,你可以指定frameInterval为2,就是说动画每隔一帧执行一次(一秒钟30帧)或者3,也就是一秒钟20次,等等。

可以在这个链接中关于CADisplayLink的部分查看更多关于CADisplayLink的用法

一、初步尝试

思路:既然CADisplayLink可以以屏幕刷新的频率调用指定selector,而且iOS系统中正常的屏幕刷新率为60Hz(60次每秒),那只要在这个方法里面统计每秒这个方法执行的次数,通过次数/时间就可以得出当前屏幕的刷新率了。

二话不说这代码我先码为敬。

(void)setupDisplayLink {

    //创建CADisplayLink,并添加到当前run loop的NSRunLoopCommonModes

    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTicks:)];

    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

}

(void)linkTicks:(CADisplayLink *)link

{

    //执行次数

    _scheduleTimes ++;

    //当前时间戳

    if(_timestamp == 0){

        _timestamp = link.timestamp;

    }

    CFTimeInterval timePassed = link.timestamp - _timestamp;

    if(timePassed >= 1.f)

        //fps

        CGFloat fps = _scheduleTimes/timePassed;

        printf("fps:%.1f, timePassed:%f\n", fps, timePassed);

        //reset

        _timestamp = link.timestamp;

        _scheduleTimes = 0;

    }

}

上述代码实现了一个简单地FPS指示器,每秒统计linkTicks方法的执行次数打印出对应的FPS。

fps:60.0, timePassed:1.015984

fps:60.0, timePassed:1.000630

fps:60.0, timePassed:1.000022

fps:60.0, timePassed:1.016638

打印结果没什么问题,好的,马上将这个类添加到以前一个在模拟器上运行很卡的Demo中验证一下,在模拟器中运行后台打印结果如下:

为了让Demo更卡,Demo中所有UIImageView都使用了圆角并设置了阴影

小结:不错,能统计到帧数的变化,这个FPS指示器也就完成了。

等等!

二、真机测试

我们还要对这个FPS指示器做更多的事情,例如在真机上测试一下。

模拟器运行在你的Mac上,然而Mac上的CPU往往比iOS设备要快。相反,Mac上的GPU和iOS设备的完全不一样,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操作在模拟器上运行的更慢,尤其是使用CAEAGLLayer来写一些OpenGL的代码时候。

这是iOS-Core-Animation-Advanced-Techniques一书中的第12节的一段话,关于动画、帧率等我们需要在真实的设备上来验证我们的代码。

二话不说我马上在真机上把这个Demo运行起来,为了能更直观的观察FPS,我将FPS显示在了屏幕上,另外运行的设备是一台运行iOS9的iPod Touch5,性能与iPhone4s差不多。

从GIF上可能不能直观的看出来,但是就本人的感受来看,除了在切换图片时有一点卡顿,其他时候都感觉挺流畅的,而且重复加载过的图片之后再加载,就不会再造成卡顿了。

在Instrument上测量的结果也是大致相同。

小结:在真机和模拟器上动画的表现确实不一样(模拟器卡,真机流畅),到此为止我们的FPS指示器仍然能正确反应屏幕的FPS。

三、极端情况

但是上面的Demo仍然不够极端,所以我们来看下面的这个Demo:在一个普通的列表里面,我们准备了1000条数据,每条数据包含了一张图片(头像)和一段文本(名字),用于在列表的Cell里面显示。每张图片都设置了圆角,且图片与文本都设置了阴影。具体代码如下:

@implementation DemoViewController- (void)viewDidLoad {    [super viewDidLoad];     //1000条记录,每条记录包含一个名字和一个头像    NSMutableArray *array = [NSMutableArray array];    for (int i = 0; i< 1000; i++) {        [array addObject:@{@"name": [self randomName], @"image": [self randomAvatar]}];    }     self.items = array;    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];} #pragma mark - UITableViewDataSource- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {    return [self.items count];} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];     NSDictionary *item = self.items[indexPath.row];    NSString *filePath = [[NSBundle mainBundle] pathForResource:item[@"image"] ofType:@"png"];     //name and image    cell.imageView.image = [UIImage imageWithContentsOfFile:filePath];    cell.textLabel.text = item[@"name"];     //image shadow    cell.imageView.layer.shadowOffset = CGSizeMake(0, 5);    cell.imageView.layer.shadowOpacity = 1;    cell.imageView.layer.cornerRadius = 5.0f;    cell.imageView.layer.masksToBounds = YES;     //text shadow    cell.textLabel.backgroundColor = [UIColor clearColor];    cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2);    cell.textLabel.layer.shadowOpacity = 1;     return cell;} - (NSString *)randomName {    NSArray *first =