CADisplayLink信号产生与回调执行

17 阅读5分钟

CADisplaylink信号是什么驱动的?如果主线程阻塞,对CADisplaylink的回调有什么影响?

一、CADisplayLink 的信号驱动:由屏幕硬件的垂直同步信号(VSync)  底层驱动

CADisplayLink 并非普通的定时器(如 NSTimer 依赖 Runloop 计时),它的触发信号直接来自屏幕的硬件刷新机制,是系统级的垂直同步信号(VSync,Vertical Synchronization)  向上层的封装,核心驱动逻辑分两层:

1. 硬件层:屏幕的固定刷新率产生 VSync 信号

iOS 设备的屏幕有固定刷新率(如 60Hz/120Hz),屏幕每完成一次整屏刷新,就会向系统内核发送一个VSync 硬件信号(60Hz 下 16.67ms 一次,120Hz 下 8.33ms 一次),这是 CADisplayLink 的原始触发源,不受 APP 进程、Runloop、主线程的控制。

2. 系统层:Core Animation 转发 VSync 信号到 CADisplayLink

系统的Core Animation 框架会持续监听内核的 VSync 信号,当接收到信号后,会将其封装为CADisplayLink 的触发事件,并将该事件投递到主线程的 Runloop 事件队列中,等待 Runloop 处理。

核心结论:CADisplayLink 的触发由屏幕硬件 + 系统内核驱动,是系统级的全局信号不受 APP 主线程状态、Runloop 阻塞的影响—— 哪怕主线程卡死,屏幕依然会按刷新率刷新,VSync 信号依然会持续产生,CADisplayLink 的 “计时” 也不会中断。

二、主线程阻塞对 CADisplayLink 回调的影响:回调被延迟 / 丢弃,而非不触发

CADisplayLink 的信号产生回调执行分离的:信号由系统驱动(不受阻塞影响),但回调代码必须在主线程 Runloop 中执行(CADisplayLink 默认绑定主线程 Runloop,且 UI 相关回调只能在主线程)。

主线程阻塞时,对 CADisplayLink 的影响分轻度阻塞重度阻塞两种情况,核心是Runloop 无法及时取出并执行 CADisplayLink 的回调事件

情况 1:主线程轻度阻塞(阻塞时间 < 一个 VSync 周期,如 60Hz 下 < 16.67ms)

  • VSync 信号正常产生,CADisplayLink 事件正常投递到主线程 Runloop 队列;
  • 但主线程正执行耗时任务(如复杂计算、同步网络),Runloop 被卡在任务处理阶段,无法取出 CADisplayLink 事件执行;
  • 当阻塞结束后,Runloop 会立即执行队列中积压的 CADisplayLink 回调,随后触发帧渲染;
  • 表现:单帧渲染延迟,若阻塞时间接近 16.67ms,会出现轻微掉帧,视觉上有短暂卡顿。

情况 2:主线程重度阻塞(阻塞时间 ≥ 一个 VSync 周期,如≥16.67ms)

  • VSync 信号持续产生,多个 CADisplayLink 事件会积压在 Runloop 队列中
  • 但 Runloop 的串行特性决定了,必须等当前耗时任务执行完毕,才能依次处理队列中的事件;
  • 若阻塞时间过长(如超过 3 个 VSync 周期),系统会丢弃部分积压的 CADisplayLink 事件(避免后续帧渲染连续执行导致的画面撕裂),仅保留最新的一个;
  • 表现连续掉帧(阻塞几个周期就掉几帧),严重时屏幕长时间停留在同一帧,出现明显的 UI 卡死,直到主线程阻塞解除。

极端情况:主线程永久阻塞(如死循环)

  • VSync 信号依然持续产生,CADisplayLink 事件持续投递,但 Runloop 永远卡在耗时任务阶段,永远无法执行 CADisplayLink 回调
  • 表现:UI 完全卡死,屏幕停留在最后一帧,直到 APP 被系统杀死(iOS 的看门狗机制 Watchdog 会检测到主线程卡死,超时后终止 APP 进程)。

三、关键补充:CADisplayLink 与 Runloop 的绑定关系

CADisplayLink 的回调能执行的前提是:被添加到一个运行的 Runloop 中(默认添加到主线程 Runloop 的NSDefaultRunLoopMode)。

  • 若主线程 Runloop未休眠 / 未阻塞,接收到 CADisplayLink 事件后,会立即执行回调,随后触发帧渲染;
  • 若主线程 Runloop被阻塞,事件会进入队列等待,这是回调延迟的核心原因;
  • 若将 CADisplayLink 添加到子线程 Runloop,其回调会在子线程执行,但无法驱动 UI 渲染(UI 渲染必须在主线程),因此实际开发中 CADisplayLink 几乎只绑定主线程。

四、对比易混点:CADisplayLink vs NSTimer,阻塞下的差异

很多人会把 CADisplayLink 和 NSTimer 混淆,二者在主线程阻塞下的表现有本质区别,核心是触发源不同

特性CADisplayLinkNSTimer
触发源屏幕硬件VSync信号(系统级)Runloop的计时(线程级)
阻塞下的信号/计时不受影响,持续产生计时中断,阻塞结束后重新计时
事件处理事件积压/丢弃,回调延迟无事件积压,回调直接延迟
与帧渲染的关联强绑定,直接驱动帧渲染无关联,仅为普通计时器

例子:60Hz 下主线程阻塞 30ms,二者表现:

  • CADisplayLink:产生 2 个 VSync 信号,2 个事件积压,阻塞结束后系统丢弃 1 个,执行 1 个回调,掉 1 帧
  • NSTimer(定时 16.67ms):计时被中断,30ms 阻塞结束后,重新开始 16.67ms 计时,仅延迟执行 1 次回调,无积压

五、核心结论梳理

  1. CADisplayLink 由屏幕硬件 VSync 信号驱动,是系统级机制,不受主线程、Runloop 状态的影响,信号会持续产生;
  2. 主线程阻塞的本质是Runloop 串行执行被卡住,导致 CADisplayLink 的回调无法及时执行(而非信号不触发);
  3. 阻塞的影响随时间递增:轻度阻塞→单帧延迟,重度阻塞→连续掉帧,永久阻塞→UI 卡死 + APP 被看门狗杀死;
  4. CADisplayLink 的回调与帧渲染强绑定,回调被延迟 / 丢弃,直接导致帧渲染的延迟 / 丢弃,这是主线程阻塞引发 UI 卡顿的核心底层原因