iOS 核心动画的性能调优

在第10章“缓冲”中,我们研究了CAMediaTimingFunction,它是一个通过控制动画缓冲来模拟物理效果例如加速或者减速来增强现实感的东西,那么如果想更加真实地模拟物理交互或者实时根据用户输入修改动画改怎么办呢?在这一章中,我们将继续探索一种能够允许我们精确地控制一帧一帧展示的基于定时器的动画。

如果你依然在编程的世界里迷茫,不知道自己的未来规划,小编给大家推荐一个IOS高级交流群:869685378里面可以与大神一起交流并走出迷茫。小白可进群免费领取学习资料,看看前辈们是如何在编程的世界里傲然前行!
群内提供数据结构与算法、底层进阶、swift、逆向、整合面试题等免费资料
附上一份收集的各大厂面试题(附答案) ! 群文件直接获取   找ios马甲包开发者合作,有兴趣请添加Q 51259559

定时帧

动画看起来是用来显示一段连续的运动过程,但实际上当在固定位置上展示像素的时候并不能做到这一点。一般来说这种显示都无法做到连续的移动,能做的仅仅是足够快地展示一系列静态图片,只是看起来像是做了运动。

我们之前提到过iOS按照每秒60次刷新屏幕,然后CAAnimation计算出需要展示的新的帧,然后在每次屏幕更新的时候同步绘制上去,CAAnimation最机智的地方在于每次刷新需要展示的时候去计算插值和缓冲。

在第10章中,我们解决了如何自定义缓冲函数,然后根据需要展示的帧的数组来告诉CAKeyframeAnimation的实例如何去绘制。所有的Core Animation实际上都是按照一定的序列来显示这些帧,那么我们可以自己做到这些么?

NSTimer

实际上,我们在第三章“图层几何学”中已经做过类似的东西,就是时钟那个例子,我们用了NSTimer来对钟表的指针做定时动画,一秒钟更新一次,但是如果我们把频率调整成一秒钟更新60次的话,原理是完全相同的。

我们来试着用NSTimer来修改第十章中弹性球的例子。由于现在我们在定时器启动之后连续计算动画帧,我们需要在类中添加一些额外的属性来存储动画的fromValuetoValueduration和当前的timeOffset(见清单11.1)。

清单11.1 使用NSTimer实现弹性球动画

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@property (nonatomic, strong) UIImageView *ballView;@property (nonatomic, strong) NSTimer *timer;@property (nonatomic, assign) NSTimeInterval duration;@property (nonatomic, assign) NSTimeInterval timeOffset;@property (nonatomic, strong) id fromValue;@property (nonatomic, strong) id toValue;@end@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //add ball image view    UIImage *ballImage = [UIImage imageNamed:@"Ball.png"];    self.ballView = [[UIImageView alloc] initWithImage:ballImage];    [self.containerView addSubview:self.ballView];    //animate    [self animate];}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    //replay animation on tap    [self animate];}float interpolate(float from, float to, float time){    return (to - from) * time + from;}- (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time{    if ([fromValue isKindOfClass:[NSValue class]]) {        //get type        const char *type = [(NSValue *)fromValue objCType];        if (strcmp(type, @encode(CGPoint)) == 0) {            CGPoint from = [fromValue CGPointValue];            CGPoint to = [toValue CGPointValue];            CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));            return [NSValue valueWithCGPoint:result];        }    }    //provide safe default implementation    return (time < 0.5)? fromValue: toValue;}float bounceEaseOut(float t){    if (t < 4/11.0) {        return (121 * t * t)/16.0;    } else if (t < 8/11.0) {        return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;    } else if (t = self.duration) {        [self.timer invalidate];        self.timer = nil;    }}@end

很赞,而且和基于关键帧例子的代码一样很多,但是如果想一次性在屏幕上对很多东西做动画,很明显就会有很多问题。

NSTimer并不是最佳方案,为了理解这点,我们需要确切地知道NSTimer是如何工作的。iOS上的每个线程都管理了一个NSRunloop,字面上看就是通过一个循环来完成一些任务列表。但是对主线程,这些任务包含如下几项:

  • 处理触摸事件

  • 发送和接受网络数据包

  • 执行使用gcd的代码

  • 处理计时器行为

  • 屏幕重绘

当你设置一个NSTimer,他会被插入到当前任务列表中,然后直到指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限,而且它只会在列表中上一个任务完成之后开始执行。这通常会导致有几毫秒的延迟,但是如果上一个任务过了很久才完成就会导致延迟很长一段时间。

屏幕重绘的频率是一秒钟六十次,但是和定时器行为一样,如果列表中上一个执行了很长时间,它也会延迟。这些延迟都是一个随机值,于是就不能保证定时器精准地一秒钟执行六十次。有时候发生在屏幕重绘之后,这就会使得更新屏幕会有个延迟,看起来就是动画卡壳了。有时候定时器会在屏幕更新的时候执行两次,于是动画

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值