首先我们来介绍一下CADisplayLink:
CADisplayLink是在QuartzCore.framework里。
CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。 CADisplayLink以特定模式注册到runloop后, 每当屏幕显示内容刷新结束的时候,runloop就会向 CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
从原理上不难看出, CADisplayLink 使用场合相对专一, 适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
注意
iOS并不能保证能以每秒60次的频率调用回调方法,这取决于:
1、CPU的空闲程度
如果CPU忙于其它计算,就没法保证以60HZ执行屏幕的绘制动作,导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。2、执行回调方法所用的时间
如果执行回调时间大于重绘每帧的间隔时间,就会导致跳过若干次回调调用机会,这取决于执行时间长短。 创建CADisplayLink
//保证计时器只存在一个 [self.timer invalidate]; self.timer = nil; self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateValue)];//将计时器放入runloop中 [self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//正如上面所说,计时器的响应方法的回调次数和频率并不一定能够保证,所以当主线程阻塞的时候要么保证动画的执行总时间,要么保证动画的总帧数 //当然我们可以将循环放入子线程来解决这个问题(理想情况下, cpu 的频率足够快,如果 cpu 在多个线程之间来回切换而 cpu 频率不够快也会导致回调方法被忽略),下篇博文我会记录下我所理解的线程问题 -(void)updateValue{ //从计时器开始执行经过了多少时间 保证执行总时间 NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; self.progress += now - self.lastUpdate; self.lastUpdate = now;//计时器响应放走实际被回调的次数 保证帧数// self.lastUpdate = now;// static NSInteger a = 0;// CGFloat progress = self.timer.duration * self.timer.frameInterval * (a++); if (self.progress >= self.totalTime) { [self.timer invalidate]; self.timer = nil;// a = 1; self.progress = self.totalTime; } [self refreshLabel]; }
最后,刷新 label 上的数字,并且根据时间来调整数字的增长速度,以及动画的速度
-(void)refreshLabel{ CGFloat progess = self.progress / self.totalTime; if (self.type == LineType) {//线性增长速度 self.timer.frameInterval = 3.5; CGFloat current = self.begin + (self.end - self.begin) * progess; self.text = [NSString stringWithFormat:@"%.2f 元",current]; } if (self.type == EasyOut) {//结束时变慢 self.timer.frameInterval = 1 + 4.5 * (1 - powf(1 - self.progress, kUILabelSpeedRate)); CGFloat current = self.begin + (self.end - self.begin) * (1 - powf(1 - progess, kUILabelSpeedRate)); self.text = [NSString stringWithFormat:@"%.2f 元",current]; } }