CADisplayLink 是一个特殊的定时器,它与屏幕的刷新频率(Vsync 信号)完全同步。在 iOS 开发中,它是处理高性能动画和游戏循环的首选工具。
1. 核心区别:同步机制与精度
虽然两者都能做定时任务,但它们的底层驱动力完全不同:
| 特性 | NSTimer | CADisplayLink |
|---|---|---|
| 驱动源 | 基于系统时钟的 RunLoop 触发 | 基于屏幕刷新率 (Vsync) |
| 精度 | 较低。受 RunLoop 任务繁忙程度影响,容易积压延迟。 | 极高。在每一帧开始刷新前触发,确保渲染连续性。 |
| 触发频率 | 自定义(如 0.1s, 2s) | 固定为屏幕刷新率(60Hz/120Hz)的子集。 |
| 使用场景 | 倒计时、延时任务、心跳包 | UI 动画、视频播放进度、游戏引擎循环 |
为什么 CADisplayLink 不掉帧?
NSTimer 的触发点是随机的。如果它在屏幕刷新周期的中段触发并执行了 UI 修改,系统必须等到下一次刷新才能把改动显示出来,这会导致视觉上的跳跃感。
而 CADisplayLink 严格在每一帧开始前触发,确保你的代码修改正好能赶上这一帧的“渲染列车”。
2. 典型使用场景
A. 自定义复杂的 UI 动画
当 UIView 动画(如 animateWithDuration)无法满足需求时,例如需要根据数学公式实时计算每一帧的位移或形变。
B. 视频/音频同步
在视频播放器中,利用 CADisplayLink 的 timestamp 属性来同步视频帧的渲染与音频进度。
C. 游戏开发
作为游戏的主循环(Game Loop),负责每一帧的物理计算、碰撞检测和屏幕重绘。
3. 使用中的关键特性
帧率控制 (PreferredFramesPerSecond)
你可以设置 preferredFramesPerSecond。如果屏幕是 60Hz,你设置 30,它就会每隔一帧触发一次。
在 iOS 15+ 的 ProMotion 屏幕上,它甚至支持动态帧率调优。
获取精准时间戳
CADisplayLink 提供了两个极其重要的属性:
-
timestamp:上一帧显示的时间。 -
targetTimestamp:下一帧即将显示的时间。利用这两个时间戳,你可以实现非常平滑的补帧动画。
4. 内存管理与注意事项
CADisplayLink 与 NSTimer 有一个共同的“陷阱”:它也会强引用 Target。
正确的销毁姿势:
由于它也依附于 RunLoop,你必须手动调用 invalidate。
Objective-C
// 1. 创建并添加到 RunLoop
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
// 2. 销毁
[self.displayLink invalidate];
self.displayLink = nil;
⚠️ 特别注意:Mode 的影响
和 NSTimer 一样,默认情况下 CADisplayLink 只在 DefaultMode 下工作。如果你在滑动列表时动画卡住,请务必将其加入到 NSRunLoopCommonModes。