Appearance
iOS Core Animation:CALayer、渲染服务与显示链路
回到总览:内存、生命周期、系统约束相关模块:Android View 绘制流程、Choreographer 与 VSync 帧同步 · SurfaceFlinger、BufferQueue 与三重缓冲:帧如何合成上屏 · 10. iOS ARC 自动引用计数与 Java/Dart GC 跨端对照
一句话定义
iOS 的 UI 渲染由 Core Animation 驱动:UIView 是对 CALayer 的薄封装,CALayer 才是真正持有几何/内容并参与合成的模型对象;App 提交 Layer 改动后,由一个独立进程/线程的渲染服务(Render Server) 在 CADisplayLink(VSync 脉冲)驱动下合成上屏——它是 iOS 侧与 Android Choreographer/SurfaceFlinger 对位的「显示链路」。
代码索引
计划补齐的实验:
labs/ios/host/topics/memory-lifecycle/ios-core-animation/— 用 Instruments 的 Core Animation 模板观察 offscreen rendering 与 commit 耗时(规划中,iOS host 待建)
为什么需要
- 为什么改了 UIView 的属性,界面却「下一帧」才变,而不是立即重绘?
- 一句话答:UIView 改动实际改的是背后 CALayer;Core Animation 把这些改动批量打包成一个事务(CATransaction) ,在RunLoop 本轮结束(或显式
commit)时一次性提交给渲染服务,由渲染服务在最近的 VSync 合成——所以视觉变化发生在「下一次提交+合成」,不是即时。
- 一句话答:UIView 改动实际改的是背后 CALayer;Core Animation 把这些改动批量打包成一个事务(CATransaction) ,在RunLoop 本轮结束(或显式
- 为什么「圆角 + 阴影 + 裁剪」叠在很多 cell 上会明显卡?
- 一句话答:这类组合会触发离屏渲染(offscreen rendering) :系统先把图层在额外离屏缓冲里预合成,再贴回,多一次 GPU Pass;列表里每个 cell 都触发,GPU 负担叠加 → 掉帧(详见 §底层机制.3)。
- 为什么 iOS 也有「VSync 对齐」,和 Android Choreographer 是同一回事吗?
- 一句话答:机制镜像但实现不同:iOS 用 CADisplayLink 对齐屏幕刷新脉冲驱动动画/提交,Android 用 Choreographer 收 VSync;目的都是「让提交/动画严格对齐硬件刷新,避免撕裂与掉帧」,只是 API 与所在进程不同。
底层机制
1. UIView → CALayer → 渲染服务
UIView 负责命中测试与布局(基于 CALayer),CALayer 负责内容与合成;App 进程提交后,渲染在独立渲染服务完成,避免主线程阻塞合成。
- 预期现象:主线程只做「提交描述」,真正的栅格化与合成在渲染服务,主线程长任务不会直接冻结合成(但会延迟提交)。
2. 隐式动画与事务(CATransaction)
Core Animation 对 Layer 的可动画属性(position/opacity/transform…)默认有隐式动画;一组改动被包进事务,在事务边界统一提交。
- 可能输出(显式事务控制动画时长)swift
CATransaction.begin() CATransaction.setAnimationDuration(0.3) layer.position = CGPoint(x: 100, y: 100) // 隐式动画生效 CATransaction.commit() - 预期现象:不加显式事务时,属性改动也会在 RunLoop 末自动提交并带默认隐式动画;批量改动应避免在动画中途零散提交,减少 commit 次数。
3. 离屏渲染(Offscreen Rendering)
圆角、阴影、遮罩、半透明叠加等会导致图层无法在常规 Pass 内直接合成,需要先离屏预合成。
- 可能输出(Instruments 中常见标记)
Color Offscreen-Rendered Yellow: 多个 cell 高亮 - 预期现象:每个触发离屏渲染的图层都多一次 GPU Pass;列表场景叠加后帧时间显著上升;用
shouldRasterize = true可把静态复杂图层缓存成位图,避免每帧重算(但会占内存、且内容变时需手动失效)。
Android / Flutter / iOS / Web 对照
| 维度 | Android | Flutter | iOS(Core Animation) | Web |
|---|---|---|---|---|
| 视图模型 | View 树 | Widget/Element/RenderObject | UIView 树 + CALayer 树 | DOM + Render 树 |
| 合成执行 | SurfaceFlinger/HWC | Impeller/Skia→SF | 渲染服务(独立进程) | Compositor |
| 帧脉冲 | Choreographer(VSync) | VsyncWaiter→Choreographer | CADisplayLink | requestAnimationFrame |
| 离屏触发 | 重叠/过度绘制 | Opacity/裁剪 | 圆角+阴影+mask | filter/3D transform |
常见场景
- 列表滚动卡顿:cell 大量圆角图片 + 阴影,每个都离屏渲染;解法:预渲染圆角图、阴影改用固定背景图、或
shouldRasterize缓存静态图层。 - 动画不跟手:主线程做了重同步任务(解密/解析),延迟了 Layer 提交,渲染服务拿不到新帧;解法:重活移出主线程/isolate,保证提交在 VSync 前完成。
- 隐式动画「意外出现」:只改了个 position 却看到平滑移动,其实是 Core Animation 默认隐式动画;不需要时应
CATransaction.setDisableActions(true)关闭。
常见误配、事故后果与排障
1. 误配:列表每个 cell 都叠圆角+阴影,触发大面积离屏渲染
- 现象:滚动 FPS 掉到 40 以下,Instruments 里 Offscreen-Rendered 大片高亮。
- 排障与修法:圆角用预渲染图或
cornerRadius+masksToBounds谨慎使用;阴影改为预渲染阴影图或shadowPath显式路径;静态复杂图层开shouldRasterize并设rasterizationScale = UIScreen.scale。
2. 误配:主线程重任务阻塞 Layer 提交
- 后果:动画/转场掉帧,因为提交晚于 VSync;与 Android「主线程长任务阻断 VSync」同源。
- 排障与修法:用 Instruments 的 Time Profiler 定位主线程热点,移出解码/解析/同步网络;保证「动画前」主线程空闲以按时提交。
3. 误配:滥用 shouldRasterize 反而更慢或失真
- 现象:开了栅格化却更卡,或内容变化后显示陈旧。
- 排障与修法:
shouldRasterize只适合静态复杂图层;内容频繁变的图层栅格化会反复重算且占内存;内容变化时需手动让缓存失效,或直接关闭。
相近概念对比
| 概念 | 它是什么 | 易混点 |
|---|---|---|
| UIView | 布局/事件的薄封装 | 真正的渲染模型是背后 CALayer |
| CALayer | 几何/内容/合成模型对象 | 不参与事件命中,只参与显示 |
| 隐式动画 | 属性改动默认带动画 | 不需要时也会触发,需 disable |
| 离屏渲染 | 额外 Pass 预合成 | 列表里叠加 = 性能杀手 |
| 渲染服务 | 独立进程合成 | 主线程只提交,不负责合成本身 |
对应实验
计划补齐的实验:
labs/ios/host/topics/memory-lifecycle/ios-core-animation/— Instruments Core Animation 模板观察 offscreen rendering 与 commit 耗时(iOS host 待建)
复习检查题
为什么说「UIView 改动不是即时重绘」,视觉变化发生在什么时候? 答:UIView 的改动实际作用于背后的 CALayer;Core Animation 把这些改动按
CATransaction批量打包,在 RunLoop 本轮结束(或显式commit)时一次性提交给渲染服务,再由渲染服务在最近的 VSync(CADisplayLink)驱动下合成上屏。所以肉眼看到的变化发生在「提交 + 下一帧合成」,而不是代码执行的瞬间。为什么「圆角 + 阴影 + 裁剪」叠在列表每个 cell 上会卡?怎么治? 答:这类组合触发离屏渲染:系统先把图层在额外离屏缓冲里预合成再贴回,多一次 GPU Pass;列表里每个 cell 都触发,Pass 叠加使帧时间上升 → 掉帧。治法:圆角用预渲染图、
shadowPath显式路径替代动态阴影、或对静态复杂图层开shouldRasterize缓存位图,减少每帧重复预合成。iOS 的 CADisplayLink 和 Android 的 Choreographer 是同一回事吗? 答:不是同一实现,但目的镜像:两者都把「提交/动画」对齐到屏幕硬件刷新脉冲,避免撕裂与掉帧。Android 由 Choreographer 在应用进程收 VSync 驱动主线程回调;iOS 由 CADisplayLink 提供对齐刷新频率的回调,真正的合成在独立渲染服务完成。一个是「应用内收脉冲」,一个是「独立服务 + 对齐脉冲」,API 与进程归属不同,解决的问题相同。
速记
- UIView≠Layer:UIView 管布局/事件,CALayer 管显示/合成。
- 提交异步:改动打包成 Transaction,下一 VSync 才上屏。
- 离屏渲染:圆角+阴影+mask = 多一次 GPU Pass,列表杀手。
- 对位 Android:CADisplayLink ↔ Choreographer;渲染服务 ↔ SurfaceFlinger。