性能
大型界面也应当用起来毫无迟滞,检查一个 App 也不应改变它的行为。两边都是围绕这一点设计的,它们的测试守住本页的数字。
Mac 上的 Cutaway
这些指标在一棵生成的、形如真实 App 的 10,000 个图层的树上测量(窗口、容器、一个导航栏和一长列单元格,每个单元格有图标、标签和分隔线),使用 Debug 构建:
| 操作 | 指标 |
|---|---|
| 图层树到达 → 列表显示出来 | 2 秒 |
| 排布场景 | 1 秒 |
| 相机移动后画出新的一帧 | 16 毫秒 |
| 绘制一帧 | 16 毫秒 |
| 选中一行 → 预览和检查器跟上 | 100 毫秒 |
| 搜索列表 | 200 毫秒 |
| 全部展开 / 全部折叠 | 1 秒 |
| 打开这棵树的存档 → 列表显示出来 | 2 秒 |
另有两项指标针对属性很多的图层:
| 操作 | 指标 |
|---|---|
| 选中一个有 1,000 个属性的图层 → 检查器显示出它们 | 0.5 秒 |
| 在这些属性上多输入一个字符过滤 | 0.1 秒 |
在开发用的 Mac 上,每次完整测试都会检查这些指标,以及下文的内存测试。CI 的运行机是更慢的虚拟 Mac,会跳过这些测试。
怎样做到
- 截图保持压缩。 模型只保存收到的压缩字节。它们在主线程之外解压、上传到 GPU,随后放掉像素。
- 截图分批到达。 Cutaway 每次请求 32 个图层的截图,两批同时在途,所以每批到达后的工作按批付出,而不是按图层。
- 长的属性列表随滚动建出。 行在进入视野时才建,所以有一千个属性的图层也能立即显示。
- 预览只在变化时绘制。 没有东西在动时,不提交任何一帧。
- 读取与绘制同步。 截图到达得比用掉得快时,Cutaway 停止从连接读取,TCP 让 App 等待。内存里不会堆积。
- 什么都不会累积。 测试在 5,000 个图层上把每种操作重复十次,检查没有留下任何截图、纹理、请求或窗口。更长的浸泡测试打印一百轮的内存曲线。
- 错误的数据不会让它崩溃。 每一帧在解码之前都经过校验,每次运行都用变异、截断和拼接的帧对解析器做模糊测试。
你的 App 里的 CutawayServer
测得的值来自 iOS 模拟器,使用 Debug 构建:
| 场景 | 指标 | 测得 |
|---|---|---|
| 一棵 10,000 个图层的树:层级,然后每个图层的图像、属性和约束——主线程最长的一次阻塞¹ | 150 毫秒 | 65–76 毫秒 |
| 同上,从请求到最后一个图层的详情 | 30 秒 | 约 8.5 秒 |
| 一个从不读取的客户端——App 内存的增长,相对 32 MiB 的预算 | 1.5 倍 | 1.07 倍 |
| 界面变化时 200 轮层级、详情、编辑和刷新——第 10 轮之后的内存增长 | 4 MiB | ≤ 0.06 MiB |
| 一次发出 1,000 个请求 | 按顺序回答;内存不随数量线性增长 | 通过 |
| 超过 100,000 个变异、截断和拼接的帧 | 不崩溃 | 通过 |
¹ 不计渲染单个图层本身的时间。
怎样做到
- 主线程保持响应。 只有必须碰 UIKit 和 Core Animation 的工作在主线程上运行,一次一个图层,所以 App 在图层之间可以处理事件。压缩、编码和网络在后台队列上运行。
- Cutaway 跟不上时内存有上限。 一旦尚未发出的数据加上等待压缩的图像超过 32 MiB,服务端就不再开始新的图像和下一个请求,直到客户端赶上来。有 64 个请求在等待时,它停止读取。
- 长时间的会话里什么都不会累积。 图层和视图以弱引用持有,连接关闭时它拥有的一切都会释放。
- 错误的输入不会让 App 崩溃。 格式错误的数据和极端的请求都以错误回答。
两者之间
任何一边都不会为另一边无限制地缓冲。Cutaway 读得比服务端发得慢时,服务端停止开始新的工作;服务端等待的请求太多时,它停止读取,TCP 的流量控制让 Cutaway 等待。规则在协议里。
