mcpField
S
swiftui-performance

swiftui-performance

github.com/dpearson2699开发1.2k4.7k 次安装13 周前更新
代码仓库

关于 swiftui-performance

帮助 SwiftUI 开发者从可复现的问题入手审查性能,并依据代码或 Instruments 跟踪证据诊断和验证改进。强调针对性修复,而非未经测量的大范围重构。

主要功能

  1. 01代码审查与性能问题分诊
  2. 02Instruments 跟踪采集和分析
  3. 03状态依赖与视图身份优化
  4. 04延迟加载和观察范围调整
  5. 05基线对比与修复验证

适用场景

  1. 01调查滚动列表更新或卡顿
  2. 02定位视图更新频繁的原因
  3. 03分析主线程上的耗时工作
  4. 04对比优化前后的性能指标

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-performance

安装后在 Claude Code 中输入/swiftui-performance确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
demystify-swiftui-performance-wwdc23.md
optimizing-swiftui-performance-instruments.md
understanding-hangs-in-your-app.md
understanding-improving-swiftui-performance.md
wwdc-session-sources.md

详细介绍

SwiftUI 性能

从可复现的问题出发,通过测量来审查和改进 SwiftUI 视图性能。动画设计请转至 swiftui-animation,生产遥测请转至 metrickit,所有权/泄漏分析请转至 ios-memgraph-analysis,导航行为请转至 swiftui-navigation,状态架构请转至 swiftui-patterns,布局构建请转至 swiftui-layout-components。

目录

工作流决策树

  • 提供了代码:先审查代码,并将发现标注为假设。
  • 只有问题症状:收集相关的最小视图、数据流、复现步骤、设备、OS 和构建配置。
  • 审查无法得出结论:在提出大范围重构建议前,先收集跟踪记录或轨道截图。

对代码和跟踪记录分析,都使用以下分诊清单:

  • 状态依赖范围过广或失效风暴
  • 列表标识不稳定或根视图条件切换
  • 在 body 中进行格式化、排序、解码或同步 I/O
  • 布局/几何反馈循环及图像尺寸过大
  • 对大型视图层级应用隐式动画

1. 代码优先审查

将分诊清单中的每个可疑项对应到具体代码。通过代码引用说明可能原因,但在跟踪记录证实开销之前,将这些发现标注为有代码依据的假设。证据不足时,建议创建最小复现或进行测量。

2. 指导用户进行性能分析

尽可能在真实设备上使用 SwiftUI Instruments 模板和 Release 构建。复现确切的交互,并根据需要捕获 SwiftUI 轨道、Time Profiler 和 Hangs/Hitches。请用户提供跟踪记录,或提供轨道和调用树的截图。

3. 分析与诊断

根据跟踪记录证据应用相同的分诊清单。将耗时较长或频繁发生的 SwiftUI 更新与 Time Profiler 调用树及复现的交互关联起来。区分有跟踪记录依据的发现与有代码依据的假设,并指出可消除剩余不确定性的下一项测量。

4. 修复

进行针对性修复:

  • 缩小状态作用范围(将 @State/@Observable 移近叶视图)。
  • 为 ForEach 和列表稳定标识。
  • 将繁重工作移出 body,放入模型层预计算、输入变化时更新的显式派生值、记忆化辅助方法或后台处理。仅当视图同时拥有该值及其更新生命周期时才使用 @State;它不是任意计算的通用缓存。
  • 仅当相等性比较比重新计算子树更省开销,且被比较的输入具有稳定的值语义时,才使用 equatable()。
  • 在渲染前对图像降采样。
  • 简化布局,或尽可能使用固定尺寸。

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

异味应查找的证据针对性修复
在 body 中格式化、排序、筛选或解码body 更新频繁或耗时较长,且调用树成本相应增加在输入变化时重新计算;在主 actor 外进行降采样或解码
UUID() 或不稳定的 id: \.self行被重新创建、状态丢失、更新过多使用稳定的模型标识
根视图的 if/else 切换切换时状态重置或更新激增在语义允许时,将条件内容或修饰符局部化
广泛读取模型许多无关视图同时更新传递范围更窄的值,或将读取移入职责明确的子视图
布局期间写入几何信息布局/更新循环反复发生为变化设置阈值,或用稳定布局替换反馈路径

5. 验证

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

输出

提供:

  • 简短的指标表(如有数据,列出优化前后)。
  • 主要问题(按影响排序)。
  • 建议的修复及预估工作量。

Instruments 性能分析

在 Instruments 中使用 SwiftUI 模板(按 Cmd+I 进行性能分析)。当前 SwiftUI 轨道包括 Update Groups、Long View Body Updates、Long Representable Updates / Representable Updates、Other Long Updates / Other Updates,以及 Cause & Effect Graph。将它们与 Time Profiler 和 Hangs/Hitches 关联分析。

在调试版本中添加 Self._printChanges(),以记录触发视图更新的属性:

var body: some View {
    #if DEBUG
    let _ = Self._printChanges()  // "MyView: @self, _count changed."
    #endif
    Text("Count: \(count)")
}

完整的性能分析工作流请参阅 references/optimizing-swiftui-performance-instruments.md。

标识与生命周期

标识决定视图的生命周期和状态。在重复内容中使用稳定的模型 ID,并仅在有意重置时更改 .id(_:)。在经过性能分析的热点行中,优先使用 @ViewBuilder 或泛型组合,而不是 AnyView。如果证据表明状态频繁变化或视图重新创建代价高昂,应将根条件分支视为可疑对象,而非直接认定为缺陷。

Text(title)
    .foregroundStyle(isHighlighted ? .yellow : .primary)

ForEach(items) { item in
    Row(item: item).id(item.stableID)
}

延迟加载模式

只有在性能分析表明急切构建、布局或更新工作影响显著时,才使用延迟容器;不存在适用于所有情况的项目数阈值。网格/列表的构建方式请转到 swiftui-layout-components。

注意事项:

  • 屏幕外的视图会从延迟堆栈中移除。SwiftUI 可能会短暂保留它们,随后删除这些视图及其视图局部状态。
  • 如果重要的行状态必须在滚动离开后保留,请将其保存在行视图之外。
  • 由于预取,onAppear 之前可能已经发生主体和布局工作。不要把 onAppear 作为行渲染所需数据的唯一设置点。
  • 将 onAppear 和 onDisappear 视为可见性信号,而非生命周期保证。
  • 在 ForEach 之前筛选数据;避免使用 if 分支让每个元素生成零行或一行。
  • 将每个 ForEach 元素的顶层子视图数量保持为固定值。必要时,使用稳定容器包装行内容。调试列表/表格慢路径时使用 -LogForEachSlowPath YES。
  • 避免假设内容尺寸或内容偏移为绝对值;延迟堆栈会估算屏幕外的尺寸。
  • 避免在延迟行中形成几何反馈循环。优先采用稳定尺寸、布局原语或自定义 Layout,再将几何变化反馈到行状态中。

状态与观察优化

Observation 会跟踪视图求值期间读取的属性。通过传递范围更窄的派生值,或将读取移入职责明确的子视图,减少影响范围。

// Split reads into child views so each tracks only what it renders.
struct ProfileView: View {
    let model: ProfileModel
    var body: some View {
        VStack {
            NameRow(model: model)      // only tracks name
            EmailRow(model: model)     // only tracks email
            AvatarView(model: model)   // only tracks avatar
            SettingsForm(model: model) // only tracks settings
        }
    }
}

廉价的计算值可以继续作为派生值;昂贵的转换则需要明确的所有者、输入集和刷新触发条件。不要把添加视图模型当作性能优化的例行做法——先进行测量,并将通用状态设计问题转到 swiftui-patterns。

常见错误

  1. 分析 Debug 构建。 Debug 构建包含额外的运行时检查,并会禁用优化,因此会产生误导性的性能数据。请在真实设备上分析 Release 构建。
  2. 只需观察一个属性,却观察整个模型。 将大型 @Observable 模型拆分为职责明确的小型模型,或使用计算属性/闭包来缩小观察范围。
  3. 在 ScrollView 项中使用几何反馈。 GeometryReader 或频繁变化的几何状态可能导致重复布局。优先使用稳定的尺寸、自定义布局,或将 .onGeometryChange (iOS 16+) 限定在较小范围内,并设置阈值。
  4. 在 body 中调用 DateFormatter() 或 NumberFormatter()。 创建这些对象的开销很大。将它们设为静态,或移到视图之外。
  5. 为不可比较状态添加动画。 如果 SwiftUI 无法判断相等性,就会在每一帧重新绘制。让状态遵循 Equatable,然后对简单的值绑定更改使用 .animation(_:value:),或对范围更窄的修饰符作用域隐式动画使用 .animation(_:body:)。
  6. 使用没有标识符的大型扁平 List。 使用 id: 或让项目遵循 Identifiable,使 SwiftUI 能够高效比较差异,而不是重建整个列表。
  7. 不必要地使用 @State 包装对象。 为了使用 @State 而将简单值类型包装在类中,会破坏值语义。对结构体使用普通的 @State。
  8. 用同步 I/O 阻塞 MainActor。 文件读取、大型负载的 JSON 解析和图像解码都应在主 actor 之外进行。优先使用非隔离的异步辅助方法或专用 actor;仅在有意打破 actor 继承并自行处理取消的情况下使用 Task.detached。

审查清单

  • body 中没有分配 DateFormatter/NumberFormatter
  • 大型列表使用遵循 Identifiable 的项目或显式的 id:
  • @Observable 模型仅公开视图实际读取的属性
  • 耗时计算在 MainActor 之外进行(图像处理、解析)
  • 延迟行具有稳定标识、固定的顶层行结构,并使用预先筛选的数据
  • 滚动行中的几何变化设有阈值,且不会影响范围过大的状态
  • 行渲染不以 onAppear 作为唯一的设置时机
  • 隐式动画对值绑定更改使用 .animation(_:value:),或对范围更窄的修饰符作用域使用 .animation(_:body:)
  • 主线程上没有同步网络/文件 I/O
  • 在真实设备上的 Release 构建中完成性能分析
  • @State 不用作未明确说明用途的缓存;每个派生值都有明确的所有者和刷新触发条件
  • 仅当比较的开销低于重新计算,且输入具有稳定的值语义时才使用 equatable()
  • 调查结论能区分基于代码的假设与基于跟踪数据的证据
  • @Observable 视图模型隔离于 @MainActor;跨并发边界的类型遵循 Sendable

参考资料

评论

支持 Markdown