ClawSkills logoClawSkills

Swiftui Performance Audit

从代码审查和架构审计并改进 SwiftUI 运行时性能。用于诊断渲染缓慢、滚动卡顿、高 CPU/内存使用率

介绍

# SwiftUI Performance Audit

_Attribution: 复制自 @Dimillian 的 `Dimillian/Skills` (2025-12-31)。_

## 概述

端到端审计 SwiftUI 视图性能,从插桩和基线测定到根本原因分析和具体的修复步骤。

## 工作流程决策树

- 如果用户提供了代码,则从“代码优先审查”开始。 - 如果用户仅描述了症状,请询问最少的代码/上下文,然后进行“代码优先审查”。 - 如果代码审查没有定论,则转至“引导用户进行性能分析”,并请求提供追踪记录或截图。

## 1. 代码优先审查

收集: - 目标视图/功能代码。 - 数据流:状态、环境、可观察模型。 - 症状和复现步骤。

重点关注: - 由广泛状态更改导致的视图失效风暴。 - 列表中的身份不稳定(`id` 频繁变动,每次渲染都使用 `UUID()`)。 - `body` 中的繁重工作(格式化、排序、图像解码)。 - 布局抖动(深层堆栈、`GeometryReader`、preference 链)。 - 未进行下采样或调整大小的大图。 - 过度动画化的层级(在大型树上的隐式动画)。

提供: - 可能的根本原因并附上代码引用。 - 建议的修复和重构方案。 - 如有需要,提供最小复现或插桩建议。

## 2. 引导用户进行性能分析

解释如何使用 Instruments 收集数据: - 在 Instruments 中使用 SwiftUI 模板(Release 构建)。 - 复现确切的交互(滚动、导航、动画)。 - 捕获 SwiftUI 时间轴和 Time Profiler。 - 导出或截图相关的通道和调用树。

询问: - SwiftUI 通道 + Time Profiler 调用树的追踪导出或截图。 - 设备/操作系统/构建配置。

## 3. 分析与诊断

优先排查可能的 SwiftUI 问题根源: - 由广泛状态更改导致的视图失效风暴。 - 列表中的身份不稳定(`id` 频繁变动,每次渲染都使用 `UUID()`)。 - `body` 中的繁重工作(格式化、排序、图像解码)。 - 布局抖动(深层堆栈、`GeometryReader`、preference 链)。 - 未进行下采样或调整大小的大图。 - 过度动画化的层级(在大型树上的隐式动画)。

使用来自追踪/日志的证据总结发现。

## 4. 修复

应用针对性的修复: - 缩小状态作用域(`@State`/`@Observable` 更靠近叶子视图)。 - 稳定 `ForEach` 和列表的身份。 - 将繁重工作移出 `body`(预计算、缓存、`@State`)。 - 为昂贵的子树使用 `equatable()` 或值包装器。 - 在渲染前对图像进行下采样。 - 降低布局复杂度,或在可能的情况下使用固定尺寸。

## 常见代码异味(及修复方法)

在代码审查期间查找这些模式。

### `body` 中的昂贵格式化程序

```swift var body: some View { let number = NumberFormatter() // slow allocation let measure = MeasurementFormatter() // slow allocation Text(measure.string(from: .init(value: meters, unit: .meters))) } ```

首选在模型或专用助手中缓存格式化程序:

```swift final class DistanceFormatter { static let shared = DistanceFormatter() let number = NumberFormatter() let measure = MeasurementFormatter() } ```

### 执行繁重工作的计算属性

```swift var filtered: [Item] { items.filter { $0.isEnabled } // runs on every body eval } ```

首选在更改时预计算或缓存:

```swift @State private var filtered: [Item] = [] // update filtered when inputs change ```

### `body` 或 `ForEach` 中的排序/过滤

```swift List { ForEach(items.sorted(by: sortRule)) { item in Row(item) } } ```

首选在视图更新前进行一次排序:

```swift let sortedItems = items.sorted(by: sortRule) ```

### `ForEach` 中的内联过滤

```swift ForEach(items.filter { $0.isEnabled }) { item in Row(item) } ```

首选使用具有稳定身份的预过滤集合。

### 身份不稳定

```swift ForEach(items, id: \.self) { item in Row(item) } ```

避免对非稳定值使用 `id: \.self`;请使用稳定的 ID。

### 主线程上的图像解码

```swift Image(uiImage: UIImage(data: data)!) ```

首选在主线程之外进行解码/下采样并存储结果。

### 可观察模型中的广泛依赖

```swift @Observable class Model { var items: [Item] = [] }

var body: some View { Row(isFavorite: model.items.contains(item)) } ```

首选细粒度的视图模型或单项状态以减少更新扩散。

## 5. 验证

请用户重新运行相同的捕获操作,并与基线指标进行比较。 如果提供了数据,总结差异(CPU、掉帧、内存峰值)。

## 输出

提供: - 一个简短的指标表(如有则包含修复前/后)。 - 主要问题(按影响排序)。 - 建议的修复方案及预估工作量。

## 参考资料

将 Apple 文档和 WWDC 资源添加到 `references/` 下,由用户提供。 - 使用 Instruments 优化 SwiftUI 性能:`references/optimizing-swiftui-performance-instruments.md` - 理解和改善 SwiftUI 性能:`references/understanding-improving-swiftui-performance.md` - 了解应用中的挂起:`references/understanding-hangs-in-your-app.md` - 揭秘 SwiftUI 性能 (WWDC23):`references/demystify-swiftui-performance-wwdc23.md`

更多产品