CADisplayLink介绍及其相关

Overview

CADisplayLink其实就是一个定时器对象,是一个能让我们以和屏幕刷新率(60HZ)同步的频率将特定的内容画到屏幕上的定时器类。

CADisplayLink 文档中也有相应介绍。CADisplayLink以addToRunLoop: forMode: 注册到runloop上,每当屏幕显示内容刷新结束,runloop就会向CADisplayLink指定的target发送消息。

Your application initializes a new display link, providing a target object and a selector to be called when the screen is updated. To synchronize your display loop with the display, your application adds it to a run loop using the addToRunLoop:forMode: method

Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated. The target can read the display link’s timestamp property to retrieve the time that the previous frame was displayed. For example, an application that displays movies might use the timestamp to calculate which video frame will be displayed next. An application that performs its own animations might use the timestamp to determine where and how displayed objects appear in the upcoming frame.

The duration property provides the amount of time between frames at the maximumFramesPerSecond. To calculate the actual frame duration, use targetTimestamp - timestamp. You can use actual frame duration in your application to calculate the frame rate of the display, the approximate time that the next frame will be displayed, and to adjust the drawing behavior so that the next frame is prepared in time to be displayed.

Your application can disable notifications by setting the paused property to YES. Also, if your application cannot provide frames in the time provided, you may want to choose a slower frame rate. An application with a slower but consistent frame rate appears smoother to the user than an application that skips frames. You can define the number of frames per second by setting the preferredFramesPerSecond property.

When your application finishes with a display link, it should call invalidate to remove it from all run loops and to disassociate it from the target.

CADisplayLink should not be subclassed.

无论是苹果的官方文档还是网上的一些资料,对DADisplayLink介绍的都很详细。

我们创建个CADisplayLink对象,并将其添加到runloop中,并提供target和selector,那么在每次屏幕刷新的时候selector就会被调用(frameInterval 默认值为1)。

同时我们也可以设置CADisplayLink对象的frameInterval属性来调整每次调用selector的间隔。

说白了,CADisplayLink就是一个跟屏幕刷新频率相同的定时器(60HZ)。CADisplayLink跟CoreAnimation类都属于QunartzCore.framework中API。

CADisplayLink的使用

1
2
3
4
5
6

self.displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(timerRunEvent)];
self.displayLink.frameInterval = 60;
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

创建CADisplayLink并将其添加到Runloop中。这样timerRunEvent(@selector)就会被周期调用,其中使用frameInterval设置调用的间隔,上方代表每秒调用一次(因为屏幕的刷新频率为60HZ,即每秒60次)。

1
2
3
4

[self.displayLink invalidate];
self.displayLink = nil;

停止,直接执行invalidate即可,类似于NSTimer的invalidate。执行完invalidate后,CADisplayLink对象从Runloop中移除,timerRunEvent(@selector)调用。

CADisplayLink与NSTimer的不同

在写这篇文章时,从网上搜了下,发现网上有很多关于CADisplayLink的介绍,而且比较详细。同时在CocoaChina上看到一篇关于iOS三种定时器的用法NSTimer、CADisplayLink、GCD的文章。

关于精度

相对于NSTimer,CADisplayLink的精度更加准确些,毕竟苹果设备的屏幕的刷新频率是固定的,都是60HZ。而CADisplayLink在每次刷新结束后都会被调用,精度会比较高。同时我们也可以根据CADisplayLink这个特性来检测屏幕是否会出现掉帧现象,如:YYKit 中计算当前界面每秒 FPS 帧数的小组件
就是根据此种原理。

关于使用场景

CADisplayLink的使用场景比较单一,适用于UI、动画的绘制与渲染。而比较著名的Facebook开源的第三方库POP就是基于CADisplayLink的,性能上比系统的CoreAnimation更加优秀。

而NSTimer在使用上就会更加的广泛了,基本很多场景都可使用。不管是一次性的还是连续周期性的timer事件,都会将NSTimer对象添加到Runloop中,但当Runloop正在执行另一个任务时,timer就会出现延迟。

最后

上面提到因为Facebook开源的动画库POP是基于CADisplayLink的,所以在开发过程中的一些定时器我们也可以使用POP来完成。比如下方的应用中经常出现的一个获取验证码的小功能,我们就可以使用POP去实现。

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

- (void)viewDidLoad {
[super viewDidLoad];

UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
self.clickBtn = btn;
[btn setTitle:@"获取验证码" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor purpleColor];
[self.view addSubview:btn];

//layout
[btn makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.size.equalTo(CGSizeMake(200, 100));
}];

//action
@weakify(self)
[btn bk_whenTapped:^{
@strongify(self)
[self timeAnimation];
}];

}

#pragma mark ---
#pragma mark ---- Animation ---
- (void)timeAnimation{

POPBasicAnimation *basicAnimation = [POPBasicAnimation linearAnimation];

POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:@"timeAnimation" initializer:^(POPMutableAnimatableProperty *prop) {

prop.writeBlock = ^(id obj, const CGFloat *values) {
UIButton *btn = (UIButton *)obj;
[btn setTitle:[NSString stringWithFormat:@"%d",(int)values[0]%60] forState:UIControlStateNormal];
};

}];

basicAnimation.property = prop;
basicAnimation.fromValue = @(10);
basicAnimation.toValue = @(0);
basicAnimation.duration = 10;

[self.clickBtn pop_addAnimation:basicAnimation forKey:@"timeAnimation"];

//开始
basicAnimation.animationDidStartBlock = ^(POPAnimation *anim) {
self.clickBtn.enabled = NO;
};

//结束
basicAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) {
self.clickBtn.enabled = YES;
[self.clickBtn setTitle:@"重新发送" forState:UIControlStateNormal];
};

}

相关Demo

参考资料