axiom-swiftui-performance
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Performance Optimization
SwiftUI性能优化
When to Use This Skill
何时使用本技能
Use when:
- App feels less responsive (hitches, hangs, delayed scrolling)
- Animations pause or jump during execution
- Scrolling performance is poor
- Profiling reveals SwiftUI is the bottleneck
- View bodies are taking too long to run
- Views are updating more frequently than necessary
- Need to understand cause-and-effect of SwiftUI updates
适用于以下场景:
- 应用响应迟缓(出现卡顿、停滞、滚动延迟)
- 动画执行时出现暂停或跳帧
- 滚动性能不佳
- 性能分析显示SwiftUI是性能瓶颈
- View body执行耗时过长
- 视图更新频率超出必要范围
- 需要了解SwiftUI更新的因果关系
Example Prompts
示例提问
These are real questions developers ask that this skill is designed to answer:
以下是开发者常问的、本技能可解答的问题:
1. "My app has janky scrolling and animations are stuttering. How do I figure out if SwiftUI is the cause?"
1. "我的应用滚动卡顿,动画掉帧。如何判断是不是SwiftUI导致的?"
→ The skill shows how to use the new SwiftUI Instrument in Instruments 26 to identify if SwiftUI is the bottleneck vs other layers
→ 本技能将展示如何使用Instruments 26中新的SwiftUI Instrument来判断性能瓶颈是否来自SwiftUI或其他层级
2. "I'm using the new SwiftUI Instrument and I see orange/red bars showing long updates. How do I know what's causing them?"
2. "我正在使用新的SwiftUI Instrument,看到橙色/红色条块表示更新耗时过长。如何找出原因?"
→ The skill covers the Cause & Effect Graph patterns that show data flow through your app and which state changes trigger expensive updates
→ 本技能会讲解因果关系图(Cause & Effect Graph)模式,展示应用中的数据流以及哪些状态变化触发了高耗时更新
3. "Some views are updating way too often even though their data hasn't changed. How do I find which views are the problem?"
3. "有些视图在数据没有变化的情况下更新过于频繁。如何找出问题视图?"
→ The skill demonstrates unnecessary update detection and Identity troubleshooting with the visual timeline
→ 本技能将演示如何通过可视化时间线检测不必要的更新,以及排查标识(Identity)相关问题
4. "I have large data structures and complex view hierarchies. How do I optimize them for SwiftUI performance?"
4. "我有大型数据结构和复杂的视图层级。如何针对SwiftUI优化它们?"
→ The skill covers performance patterns: breaking down view hierarchies, minimizing body complexity, and using the @Sendable optimization checklist
→ 本技能涵盖性能优化模式:拆分视图层级、最小化body复杂度,以及使用@Sendable优化检查清单
5. "We have a performance deadline and I need to understand what's slow in SwiftUI. What are the critical metrics?"
5. "我们面临性能达标期限,需要了解SwiftUI中哪些部分速度慢。关键指标有哪些?"
→ The skill provides the decision tree for prioritizing optimizations and understands pressure scenarios with professional guidance for trade-offs
→ 本技能提供优化优先级决策树,并结合专业指导分析压力场景下的权衡方案
Overview
概述
Core Principle: Ensure your view bodies update quickly and only when needed to achieve great SwiftUI performance.
NEW in WWDC 2025: Next-generation SwiftUI instrument in Instruments 26 provides comprehensive performance analysis with:
- Visual timeline of long updates (color-coded orange/red by severity)
- Cause & Effect Graph showing data flow through your app
- Integration with Time Profiler for CPU analysis
- Hangs and Hitches tracking
Key Performance Problems:
- Long View Body Updates — View bodies taking too long to run
- Unnecessary View Updates — Views updating when data hasn't actually changed
核心原则:确保你的view body更新迅速且仅在必要时更新,以实现出色的SwiftUI性能。
WWDC 2025 新特性:Instruments 26中的新一代SwiftUI Instrument提供全面的性能分析功能,包括:
- 高耗时更新的可视化时间线(按严重程度用橙色/红色标记)
- 展示应用数据流的因果关系图
- 与Time Profiler集成进行CPU分析
- 卡顿(Hangs)和掉帧(Hitches)跟踪
关键性能问题:
- View Body更新耗时过长 —— View body执行时间过久
- 不必要的视图更新 —— 数据未发生变化时视图仍更新
iOS 26 Framework Performance Improvements
iOS 26框架性能改进
"Performance improvements to the framework benefit apps across all of Apple's platforms, from our app to yours." — WWDC 2025-256
SwiftUI in iOS 26 includes major performance wins that benefit all apps automatically. These improvements work alongside the new profiling tools to make SwiftUI faster out of the box.
"框架层面的性能提升将惠及苹果全平台的应用,从我们的应用到你的应用。" —— WWDC 2025-256
iOS 26中的SwiftUI包含重大性能提升,所有应用均可自动受益。这些改进与新的性能分析工具相辅相成,让SwiftUI在开箱即用的情况下更快。
List Performance (macOS Focus)
List性能优化(重点针对macOS)
Massive gains for large lists
大型列表的大幅提升
- 6x faster loading for lists of 100,000+ items on macOS
- 16x faster updates for large lists
- Even bigger gains for larger lists
- Improvements benefit all platforms (iOS, iPadOS, watchOS, not just macOS)
swift
List(trips) { trip in // 100k+ items
TripRow(trip: trip)
}
// iOS 26: Loads 6x faster, updates 16x faster on macOS
// All platforms benefit from performance improvements- 加载速度提升6倍:macOS上包含100,000+条数据的列表
- 更新速度提升16倍:大型列表的更新操作
- 数据量越大,提升越显著
- 改进惠及所有平台(iOS、iPadOS、watchOS,不仅限于macOS)
swift
List(trips) { trip in // 10万+条数据
TripRow(trip: trip)
}
// iOS 26: macOS上加载速度提升6倍,更新速度提升16倍
// 所有平台均能获得性能提升Impact on your app
对应用的影响
- Large datasets (10k+ items) see noticeable improvements
- Filtering and sorting operations complete faster
- Real-time updates to lists are more responsive
- Benefits apps like file browsers, contact lists, data tables
- 大型数据集(1万+条数据)的性能提升明显
- 筛选和排序操作完成速度更快
- 列表的实时更新响应更迅速
- 惠及文件浏览器、联系人列表、数据表类应用
Scrolling Performance
滚动性能
Reduced dropped frames during high-speed scrolling
高速滚动时减少掉帧
SwiftUI has improved scheduling of user interface updates on iOS and macOS. This improves responsiveness and lets SwiftUI do even more work to prepare for upcoming frames. All in all, it reduces the chance of your app dropping a frame while scrolling quickly at high frame rates.
SwiftUI改进了iOS和macOS上用户界面更新的调度机制。这提升了响应速度,让SwiftUI有更多时间为即将到来的帧做准备。总体而言,这降低了应用在高帧率下快速滚动时的掉帧概率。
Key improvements
关键改进点
- Better frame scheduling — SwiftUI gets more time to prepare for upcoming frames
- Improved responsiveness — UI updates scheduled more efficiently
- Fewer dropped frames — Especially during quick scrolling at 120Hz (ProMotion)
- 更优的帧调度 —— SwiftUI获得更多时间为即将到来的帧做准备
- 响应速度提升 —— UI更新调度更高效
- 掉帧减少 —— 尤其是在120Hz(ProMotion)设备上快速滚动时
When you'll notice
可感知提升的场景
- Scrolling through image-heavy content
- High frame rate devices (iPhone Pro, iPad Pro with ProMotion)
- Complex list rows with multiple views
- 滚动包含大量图片的内容时
- 高帧率设备(iPhone Pro、配备ProMotion的iPad Pro)
- 包含多个视图的复杂列表行
Nested ScrollViews with Lazy Stacks
嵌套ScrollView与Lazy Stacks
Photo carousels and multi-axis scrolling now properly optimize
照片轮播和多轴滚动现在可正确优化
swift
ScrollView(.horizontal) {
LazyHStack {
ForEach(photoSets) { photoSet in
ScrollView(.vertical) {
LazyVStack {
ForEach(photoSet.photos) { photo in
PhotoView(photo: photo)
}
}
}
}
}
}
// iOS 26: Nested scrollviews now properly delay loading with lazy stacks
// Great for photo carousels, Netflix-style layouts, multi-axis contentBefore iOS 26 Nested ScrollViews didn't properly delay loading lazy stack content, causing all nested content to load immediately.
After iOS 26 Lazy stacks inside nested ScrollViews now delay loading until content is about to appear, matching the behavior of single-level ScrollViews.
swift
ScrollView(.horizontal) {
LazyHStack {
ForEach(photoSets) { photoSet in
ScrollView(.vertical) {
LazyVStack {
ForEach(photoSet.photos) { photo in
PhotoView(photo: photo)
}
}
}
}
}
}
// iOS 26: 嵌套ScrollView现在可正确延迟Lazy Stack内容的加载
// 非常适合照片轮播、Netflix式布局、多轴内容展示iOS 26之前:嵌套ScrollView无法正确延迟Lazy Stack内容的加载,导致所有嵌套内容立即加载。
iOS 26之后:嵌套ScrollView内的Lazy Stacks会延迟加载,直到内容即将显示,与单层ScrollView的行为一致。
Use cases
适用场景
- Photo galleries with horizontal/vertical scrolling
- Netflix-style category rows
- Multi-dimensional data browsers
- Image carousels with vertical detail scrolling
- 支持水平/垂直滚动的照片图库
- Netflix式分类行布局
- 多维数据浏览器
- 支持垂直详情滚动的图片轮播
SwiftUI Performance Instrument Enhancements
SwiftUI性能分析工具增强
New lanes in Instruments 26
Instruments 26中的新追踪通道
The SwiftUI instrument now includes dedicated lanes for:
- Long View Body Updates — Identify expensive body computations
- Platform View Updates — Track UIKit/AppKit bridging performance (Long Representable Updates)
- Other Long Updates — All other types of long SwiftUI work
These lanes are covered in detail in the next section.
SwiftUI Instrument现在包含专用的追踪通道:
- Long View Body Updates —— 识别耗时的body计算
- Platform View Updates —— 跟踪UIKit/AppKit桥接性能(Long Representable Updates)
- Other Long Updates —— 所有其他类型的高耗时SwiftUI操作
这些通道将在下一节详细介绍。
Performance Improvement Summary
性能提升总结
Automatic wins (recompile only)
自动获得的提升(仅需重新编译)
- ✅ 6x faster list loading (100k+ items, macOS)
- ✅ 16x faster list updates (macOS)
- ✅ Reduced dropped frames during scrolling
- ✅ Improved frame scheduling on iOS/macOS
- ✅ Nested ScrollView lazy loading optimization
No code changes required — rebuild with iOS 26 SDK to get these improvements.
Cross-reference SwiftUI 26 Features (swiftui-26-ref skill) — Comprehensive guide to all iOS 26 SwiftUI changes
- ✅ 大型列表加载速度提升6倍(10万+条数据,macOS)
- ✅ 大型列表更新速度提升16倍(macOS)
- ✅ 滚动时掉帧减少
- ✅ iOS/macOS上的帧调度优化
- ✅ 嵌套ScrollView的Lazy加载优化
无需修改代码 —— 使用iOS 26 SDK重新构建应用即可获得这些改进。
交叉参考 SwiftUI 26特性(swiftui-26-ref技能)—— iOS 26 SwiftUI所有变更的综合指南
The SwiftUI Instrument (Instruments 26)
SwiftUI Instrument(Instruments 26)
Getting Started
入门指南
Requirements:
- Install Xcode 26
- Update devices to latest OS releases (support for recording SwiftUI traces)
- Build app in Release mode for accurate profiling
Launch:
- Open project in Xcode
- Press Command-I to profile
- Choose SwiftUI template from template chooser
- Click Record button
要求:
- 安装Xcode 26
- 将设备更新至最新OS版本(支持录制SwiftUI追踪数据)
- 以Release模式构建应用以获得准确的性能分析结果
启动步骤:
- 在Xcode中打开项目
- 按下 Command-I 开始性能分析
- 从模板选择器中选择 SwiftUI模板
- 点击录制按钮
Template Contents
模板内容
The SwiftUI template includes three instruments:
- SwiftUI Instrument (NEW) — Identifies performance issues in SwiftUI code
- Time Profiler — Shows CPU work samples over time
- Hangs and Hitches — Tracks app responsiveness
SwiftUI模板包含三个工具:
- SwiftUI Instrument(新增) —— 识别SwiftUI代码中的性能问题
- Time Profiler —— 显示随时间变化的CPU工作样本
- Hangs and Hitches —— 跟踪应用响应性
SwiftUI Instrument Track Lanes
SwiftUI Instrument追踪通道
Lane 1: Update Groups
通道1:Update Groups
- Shows when SwiftUI is actively doing work
- Empty during CPU spikes? → Problem likely outside SwiftUI
- 显示SwiftUI何时在主动执行工作
- CPU峰值期间无内容? → 问题可能来自SwiftUI之外
Lane 2: Long View Body Updates
通道2:Long View Body Updates
- Highlights when property takes too long
body - Most common performance issue — start here
- 高亮显示属性耗时过长的情况
body - 最常见的性能问题 —— 从这里开始排查
Lane 3: Long Representable Updates
通道3:Long Representable Updates
- Identifies slow UIViewRepresentable/NSViewRepresentable updates
- UIKit/AppKit integration performance
- 识别缓慢的UIViewRepresentable/NSViewRepresentable更新
- UIKit/AppKit集成性能
Lane 4: Other Long Updates
通道4:Other Long Updates
- All other types of long SwiftUI work
- 所有其他类型的高耗时SwiftUI操作
Color-Coding System
颜色编码系统
Updates shown in orange and red based on likelihood to cause hitches:
- Red — Very likely to contribute to hitch/hang (investigate first)
- Orange — Moderately likely to cause issues
- Gray — Normal updates, not concerning
Note: Whether updates actually result in hitches depends on device conditions, but red updates are the highest priority.
更新操作根据导致掉帧的可能性用橙色和红色标记:
- 红色 —— 极有可能导致卡顿/停滞(优先排查)
- 橙色 —— 中等可能导致问题
- 灰色 —— 正常更新,无需关注
注意:更新是否真的会导致掉帧取决于设备状态,但红色更新是最高优先级。
Understanding the Render Loop
理解渲染循环
Normal Frame Rendering
正常帧渲染
Frame 1:
├─ Handle events (touches, key presses)
├─ Update UI (run view bodies)
│ └─ Complete before frame deadline ✅
├─ Hand off to system
└─ System renders → Visible on screen
Frame 2:
├─ Handle events
├─ Update UI
│ └─ Complete before frame deadline ✅
├─ Hand off to system
└─ System renders → Visible on screenResult: Smooth, fluid animations
Frame 1:
├─ 处理事件(触摸、按键)
├─ 更新UI(运行view bodies)
│ └─ 在帧截止时间前完成 ✅
├─ 移交至系统
└─ 系统渲染 → 显示在屏幕上
Frame 2:
├─ 处理事件
├─ 更新UI
│ └─ 在帧截止时间前完成 ✅
├─ 移交至系统
└─ 系统渲染 → 显示在屏幕上结果:流畅的动画
Frame with Hitch (Long View Body)
出现掉帧的帧(View Body耗时过长)
Frame 1:
├─ Handle events
├─ Update UI
│ └─ ONE VIEW BODY TOO SLOW
│ └─ Runs past frame deadline ❌
├─ Miss deadline
└─ Previous frame stays visible (HITCH)
Frame 2: (Delayed)
├─ Handle events (delayed by 1 frame)
├─ Update UI
├─ Hand off to system
└─ System renders → Finally visible
Result: Previous frame visible for 2+ frames = animation stutterFrame 1:
├─ 处理事件
├─ 更新UI
│ └─ 某个VIEW BODY速度过慢
│ └─ 超出帧截止时间 ❌
├─ 错过截止时间
└─ 上一帧保持显示(掉帧)
Frame 2: (延迟)
├─ 处理事件(延迟1帧)
├─ 更新UI
├─ 移交至系统
└─ 系统渲染 → 最终显示
结果:上一帧显示2+帧时长 = 动画卡顿Frame with Hitch (Too Many Updates)
出现掉帧的帧(更新次数过多)
Frame 1:
├─ Handle events
├─ Update UI
│ ├─ Update 1 (fast)
│ ├─ Update 2 (fast)
│ ├─ Update 3 (fast)
│ ├─ ... (100 more fast updates)
│ └─ Total time exceeds deadline ❌
├─ Miss deadline
└─ Previous frame stays visible (HITCH)Result: Many small updates add up to miss deadline
Key Insight: View body runtime matters because missing frame deadlines causes hitches, making animations less fluid.
Reference:
- Understanding hitches in your app
- Tech Talk on render loop and fixing hitches
Frame 1:
├─ 处理事件
├─ 更新UI
│ ├─ 更新1(快速)
│ ├─ 更新2(快速)
│ ├─ 更新3(快速)
│ ├─ ...(100次更多快速更新)
│ └─ 总耗时超出截止时间 ❌
├─ 错过截止时间
└─ 上一帧保持显示(掉帧)结果:多次小更新累加导致错过截止时间
关键洞察:View body的运行时间很重要,因为错过帧截止时间会导致掉帧,让动画不够流畅。
参考:
- 了解应用中的掉帧问题
- 关于渲染循环和修复掉帧的技术讲座
Problem 1: Long View Body Updates
问题1:View Body更新耗时过长
Identifying Long Updates
识别高耗时更新
- Record trace in Instruments with SwiftUI template
- Look at Long View Body Updates lane — any orange/red bars?
- Expand SwiftUI track to see subtracks
- Select View Body Updates subtrack
- Filter to long updates:
- Detail pane → Dropdown → Choose "Long View Body Updates summary"
- 录制追踪数据:使用Instruments的SwiftUI模板
- 查看Long View Body Updates通道 —— 是否有橙色/红色条块?
- 展开SwiftUI追踪轨道查看子轨道
- 选择View Body Updates子轨道
- 筛选高耗时更新:
- 详情面板 → 下拉菜单 → 选择"Long View Body Updates summary"
Analyzing with Time Profiler
结合Time Profiler分析
Workflow:
- Find long update in Long View Body Updates summary
- Hover over view name → Click arrow → "Show Updates"
- Right-click on long update → "Set Inspection Range and Zoom"
- Switch to Time Profiler instrument track
What you see:
- Call stacks for samples recorded during view body execution
- Time spent in each frame (leftmost column)
- Your view body nested in deep SwiftUI call stack
Finding the bottleneck:
- Option-click to expand main thread call stack
- Command-F to search for your view name (e.g., "LandmarkListItemView")
- Identify expensive operations in time column
工作流程:
- 在Long View Body Updates摘要中找到高耗时更新
- 悬停在视图名称上 → 点击箭头 → "Show Updates"
- 右键点击高耗时更新 → "Set Inspection Range and Zoom"
- 切换到Time Profiler工具轨道
你将看到:
- View body执行期间记录的调用栈
- 每帧的耗时(最左侧列)
- 嵌套在深层SwiftUI调用栈中的你的view body
定位瓶颈:
- 按住Option键展开主线程调用栈
- 使用Command-F搜索你的视图名称(例如"LandmarkListItemView")
- 在时间列中识别高耗时操作
Common Expensive Operations
常见高耗时操作
Formatter Creation (Very Expensive)
格式化器创建(非常耗时)
❌ WRONG - Creating formatters in view body:
swift
struct LandmarkListItemView: View {
let landmark: Landmark
@State private var userLocation: CLLocation
var distance: String {
// ❌ Creating formatters every time body runs
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 1
let measurementFormatter = MeasurementFormatter()
measurementFormatter.numberFormatter = numberFormatter
let meters = userLocation.distance(from: landmark.location)
let measurement = Measurement(value: meters, unit: UnitLength.meters)
return measurementFormatter.string(from: measurement)
}
var body: some View {
HStack {
Text(landmark.name)
Text(distance) // Calls expensive distance property
}
}
}Why it's slow:
- Formatters are expensive to create (milliseconds each)
- Created every time view body runs
- Runs on main thread → app waits before continuing UI updates
- Multiple views → time adds up quickly
✅ CORRECT - Cache formatters centrally:
swift
@Observable
class LocationFinder {
private let formatter: MeasurementFormatter
private let landmarks: [Landmark]
private var distanceCache: [Landmark.ID: String] = [:]
init(landmarks: [Landmark]) {
self.landmarks = landmarks
// Create formatters ONCE during initialization
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 1
self.formatter = MeasurementFormatter()
self.formatter.numberFormatter = numberFormatter
updateDistances()
}
func didUpdateLocations(_ locations: [CLLocation]) {
guard let location = locations.last else { return }
updateDistances(from: location)
}
private func updateDistances(from location: CLLocation? = nil) {
guard let location else { return }
for landmark in landmarks {
let meters = location.distance(from: landmark.location)
let measurement = Measurement(value: meters, unit: UnitLength.meters)
distanceCache[landmark.id] = formatter.string(from: measurement)
}
}
func distanceString(for landmarkID: Landmark.ID) -> String {
distanceCache[landmarkID] ?? "Unknown"
}
}
struct LandmarkListItemView: View {
let landmark: Landmark
@Environment(LocationFinder.self) private var locationFinder
var body: some View {
HStack {
Text(landmark.name)
Text(locationFinder.distanceString(for: landmark.id)) // ✅ Fast lookup
}
}
}Benefits:
- Formatters created once, reused for all landmarks
- Strings pre-calculated when location changes
- View body just reads cached value (instant)
- Long view body updates eliminated
❌ 错误做法 - 在view body中创建格式化器:
swift
struct LandmarkListItemView: View {
let landmark: Landmark
@State private var userLocation: CLLocation
var distance: String {
// ❌ 每次body运行时都创建格式化器
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 1
let measurementFormatter = MeasurementFormatter()
measurementFormatter.numberFormatter = numberFormatter
let meters = userLocation.distance(from: landmark.location)
let measurement = Measurement(value: meters, unit: UnitLength.meters)
return measurementFormatter.string(from: measurement)
}
var body: some View {
HStack {
Text(landmark.name)
Text(distance) // 调用高耗时的distance属性
}
}
}为什么慢:
- 格式化器创建成本高(每个耗时数毫秒)
- 每次view body运行时都会创建
- 在主线程运行 → 应用需等待才能继续UI更新
- 多个视图的耗时会快速累加
✅ 正确做法 - 集中缓存格式化器:
swift
@Observable
class LocationFinder {
private let formatter: MeasurementFormatter
private let landmarks: [Landmark]
private var distanceCache: [Landmark.ID: String] = [:]
init(landmarks: [Landmark]) {
self.landmarks = landmarks
// 初始化时仅创建一次格式化器
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 1
self.formatter = MeasurementFormatter()
self.formatter.numberFormatter = numberFormatter
updateDistances()
}
func didUpdateLocations(_ locations: [CLLocation]) {
guard let location = locations.last else { return }
updateDistances(from: location)
}
private func updateDistances(from location: CLLocation? = nil) {
guard let location else { return }
for landmark in landmarks {
let meters = location.distance(from: landmark.location)
let measurement = Measurement(value: meters, unit: UnitLength.meters)
distanceCache[landmark.id] = formatter.string(from: measurement)
}
}
func distanceString(for landmarkID: Landmark.ID) -> String {
distanceCache[landmarkID] ?? "Unknown"
}
}
struct LandmarkListItemView: View {
let landmark: Landmark
@Environment(LocationFinder.self) private var locationFinder
var body: some View {
HStack {
Text(landmark.name)
Text(locationFinder.distanceString(for: landmark.id)) // ✅ 快速查找缓存值
}
}
}优势:
- 格式化器仅创建一次,供所有地标复用
- 位置变化时预先计算字符串
- View body仅读取缓存值(瞬间完成)
- 消除了View body高耗时更新问题
Other Expensive Operations
其他高耗时操作
Complex Calculations:
swift
// ❌ Don't calculate in view body
var body: some View {
let result = expensiveAlgorithm(data) // Complex math, sorting, etc.
Text("\(result)")
}
// ✅ Calculate in model, cache result
@Observable
class ViewModel {
private(set) var result: Int = 0
func updateData(_ data: [Int]) {
result = expensiveAlgorithm(data) // Calculate once
}
}Network/File I/O:
swift
// ❌ NEVER do I/O in view body
var body: some View {
let data = try? Data(contentsOf: fileURL) // ❌ Synchronous I/O
// ...
}
// ✅ Load asynchronously, store in state
@State private var data: Data?
var body: some View {
// Just read state
}
.task {
data = try? await loadData() // Async loading
}Image Processing:
swift
// ❌ Don't process images in view body
var body: some View {
let thumbnail = image.resized(to: CGSize(width: 100, height: 100))
Image(uiImage: thumbnail)
}
// ✅ Process images in background, cache
.task {
await processThumbnails()
}复杂计算:
swift
// ❌ 不要在view body中计算
var body: some View {
let result = expensiveAlgorithm(data) // 复杂数学运算、排序等
Text("\(result)")
}
// ✅ 在模型中计算并缓存结果
@Observable
class ViewModel {
private(set) var result: Int = 0
func updateData(_ data: [Int]) {
result = expensiveAlgorithm(data) // 仅计算一次
}
}网络/文件I/O:
swift
// ❌ 绝对不要在view body中进行I/O操作
var body: some View {
let data = try? Data(contentsOf: fileURL) // ❌ 同步I/O
// ...
}
// ✅ 异步加载并存储在状态中
@State private var data: Data?
var body: some View {
// 仅读取状态
}
.task {
data = try? await loadData() // 异步加载
}图片处理:
swift
// ❌ 不要在view body中处理图片
var body: some View {
let thumbnail = image.resized(to: CGSize(width: 100, height: 100))
Image(uiImage: thumbnail)
}
// ✅ 在后台处理图片并缓存
.task {
await processThumbnails()
}Verifying the Fix
验证修复效果
After implementing fix:
- Record new trace in Instruments
- Check Long View Body Updates summary
- Verify your view is gone from the list (or significantly reduced)
Note: Updates at app launch may still be long (building initial view hierarchy) — this is normal and won't cause hitches during scrolling.
实施修复后:
- 使用Instruments录制新的追踪数据
- 检查Long View Body Updates摘要
- 确认你的视图已从列表中消失(或耗时大幅减少)
注意:应用启动时的更新可能仍耗时较长(构建初始视图层级)——这是正常现象,不会导致滚动时的卡顿。
Problem 2: Unnecessary View Updates
问题2:不必要的视图更新
Why Unnecessary Updates Matter
不必要的更新为何重要
Even if individual updates are fast, too many updates add up:
100 fast updates × 2ms each = 200ms total
→ Misses 16.67ms frame deadline
→ Hitch即使单个更新速度快,过多的更新累加起来也会有问题:
100次快速更新 × 每次2ms = 总耗时200ms
→ 错过16.67ms的帧截止时间
→ 掉帧Identifying Unnecessary Updates
识别不必要的更新
Scenario: Tapping a favorite button on one item updates ALL items in a list.
Expected: Only the tapped item updates.
Actual: All visible items update.
How to find:
- Record trace with user interaction in mind
- Highlight relevant portion of timeline
- Expand hierarchy in detail pane
- Count updates — more than expected?
场景:点击一个项目的收藏按钮后,列表中的所有项目都更新了。
预期:仅被点击的项目更新。
实际:所有可见项目都更新了。
查找方法:
- 录制包含用户交互的追踪数据
- 高亮时间线中的相关部分
- 在详情面板中展开层级
- 统计更新次数 —— 是否超出预期?
Understanding SwiftUI's Data Model
理解SwiftUI的数据模型
SwiftUI uses AttributeGraph to define dependencies and avoid re-running views unnecessarily.
SwiftUI使用AttributeGraph来定义依赖关系,避免不必要地重新运行视图。
Attributes & Dependencies
属性与依赖关系
swift
struct OnOffView: View {
@State private var isOn: Bool = false
var body: some View {
Text(isOn ? "On" : "Off")
}
}What SwiftUI creates:
- View attribute — Stores view struct (recreated frequently)
- State storage — Keeps value (persists entire view lifetime)
isOn - Signal attribute — Tracks when state changes
- View body attribute — Depends on state signal
- Text attributes — Depend on view body
When state changes:
- Create transaction (scheduled change for next frame)
- Mark signal attribute as outdated
- Walk dependency chain, marking dependent attributes as outdated (just set flag - fast)
- Before rendering, update all outdated attributes
- View body runs again, producing new Text struct
- Continue updates until all needed attributes updated
- Render frame
swift
struct OnOffView: View {
@State private var isOn: Bool = false
var body: some View {
Text(isOn ? "On" : "Off")
}
}SwiftUI创建的内容:
- View attribute —— 存储视图结构体(频繁重建)
- State storage —— 保存的值(在视图整个生命周期中持久化)
isOn - Signal attribute —— 跟踪状态变化
- View body attribute —— 依赖于状态信号
- Text attributes —— 依赖于view body
状态变化时:
- 创建事务(安排在下一帧执行变更)
- 将信号属性标记为过时
- 遍历依赖链,将依赖属性标记为过时(仅设置标志 - 快速)
- 渲染前,更新所有过时属性
- View body再次运行,生成新的Text结构体
- 继续更新直到所有需要的属性都更新完成
- 渲染帧
The Cause & Effect Graph
因果关系图
Purpose: Visualize what marked your view body as outdated.
Example graph:
[Gesture] → [State Change] → [View Body Update]
↓
[Other View Bodies]Node types:
- Blue nodes — Your code or actions (gestures, state changes, view bodies)
- System nodes — SwiftUI/system work
- Arrows labeled "update" — Caused update
- Arrows labeled "creation" — Caused view to appear
Selecting nodes:
- Click State change node → See backtrace of where value was updated
- Click View body node → See which views updated and why
Accessing graph:
- Detail pane → Expand hierarchy to find view
- Hover over view name → Click arrow
- Choose "Show Cause & Effect Graph"
用途:可视化是什么导致你的view body被标记为过时。
示例图:
[Gesture] → [State Change] → [View Body Update]
↓
[Other View Bodies]节点类型:
- 蓝色节点 —— 你的代码或操作(手势、状态变化、view bodies)
- 系统节点 —— SwiftUI/系统工作
- 标记为"update"的箭头 —— 导致更新
- 标记为"creation"的箭头 —— 导致视图出现
选择节点:
- 点击 State change节点 → 查看值被更新的回溯信息
- 点击 View body节点 → 查看哪些视图更新了以及原因
访问图的方法:
- 详情面板 → 展开层级找到视图
- 悬停在视图名称上 → 点击箭头
- 选择 "Show Cause & Effect Graph"
Example: Favorites List Problem
示例:收藏列表问题
Problem:
swift
@Observable
class ModelData {
var favoritesCollection: Collection // Contains array of favorites
func isFavorite(_ landmark: Landmark) -> Bool {
favoritesCollection.landmarks.contains(landmark) // ❌ Depends on whole array
}
}
struct LandmarkListItemView: View {
let landmark: Landmark
@Environment(ModelData.self) private var modelData
var body: some View {
HStack {
Text(landmark.name)
Button {
modelData.toggleFavorite(landmark) // Modifies array
} label: {
Image(systemName: modelData.isFavorite(landmark) ? "heart.fill" : "heart")
}
}
}
}What happens:
- Each view calls , accessing
isFavorite()arrayfavoritesCollection.landmarks - creates dependency: Each view depends on entire array
@Observable - Tapping button calls , modifying array
toggleFavorite() - All views marked as outdated (array changed)
- All view bodies run (even though only one changed)
Cause & Effect Graph shows:
[Gesture] → [favoritesCollection.landmarks array change] → [All LandmarkListItemViews update]✅ Solution — Granular Dependencies:
swift
@Observable
class LandmarkViewModel {
var isFavorite: Bool = false
func toggleFavorite() {
isFavorite.toggle()
}
}
@Observable
class ModelData {
private(set) var viewModels: [Landmark.ID: LandmarkViewModel] = [:]
init(landmarks: [Landmark]) {
for landmark in landmarks {
viewModels[landmark.id] = LandmarkViewModel()
}
}
func viewModel(for landmarkID: Landmark.ID) -> LandmarkViewModel? {
viewModels[landmarkID]
}
}
struct LandmarkListItemView: View {
let landmark: Landmark
@Environment(ModelData.self) private var modelData
var body: some View {
if let viewModel = modelData.viewModel(for: landmark.id) {
HStack {
Text(landmark.name)
Button {
viewModel.toggleFavorite() // ✅ Only modifies this view model
} label: {
Image(systemName: viewModel.isFavorite ? "heart.fill" : "heart")
}
}
}
}
}Result:
- Each view depends only on its own view model
- Tapping button updates only that view model
- Only one view body runs
Cause & Effect Graph shows:
[Gesture] → [Single LandmarkViewModel change] → [Single LandmarkListItemView update]问题:
swift
@Observable
class ModelData {
var favoritesCollection: Collection // 包含收藏项数组
func isFavorite(_ landmark: Landmark) -> Bool {
favoritesCollection.landmarks.contains(landmark) // ❌ 依赖整个数组
}
}
struct LandmarkListItemView: View {
let landmark: Landmark
@Environment(ModelData.self) private var modelData
var body: some View {
HStack {
Text(landmark.name)
Button {
modelData.toggleFavorite(landmark) // 修改数组
} label: {
Image(systemName: modelData.isFavorite(landmark) ? "heart.fill" : "heart")
}
}
}
}发生的情况:
- 每个视图都调用,访问
isFavorite()数组favoritesCollection.landmarks - 创建依赖关系:每个视图都依赖整个数组
@Observable - 点击按钮调用,修改数组
toggleFavorite() - 所有视图都被标记为过时(数组已更改)
- 所有view body都运行(即使只有一个项目发生了变化)
因果关系图显示:
[Gesture] → [favoritesCollection.landmarks数组变化] → [所有LandmarkListItemViews更新]✅ 解决方案 —— 细粒度依赖:
swift
@Observable
class LandmarkViewModel {
var isFavorite: Bool = false
func toggleFavorite() {
isFavorite.toggle()
}
}
@Observable
class ModelData {
private(set) var viewModels: [Landmark.ID: LandmarkViewModel] = [:]
init(landmarks: [Landmark]) {
for landmark in landmarks {
viewModels[landmark.id] = LandmarkViewModel()
}
}
func viewModel(for landmarkID: Landmark.ID) -> LandmarkViewModel? {
viewModels[landmarkID]
}
}
struct LandmarkListItemView: View {
let landmark: Landmark
@Environment(ModelData.self) private var modelData
var body: some View {
if let viewModel = modelData.viewModel(for: landmark.id) {
HStack {
Text(landmark.name)
Button {
viewModel.toggleFavorite() // ✅ 仅修改该视图模型
} label: {
Image(systemName: viewModel.isFavorite ? "heart.fill" : "heart")
}
}
}
}
}结果:
- 每个视图仅依赖自己的视图模型
- 点击按钮仅更新该视图模型
- 仅一个view body运行
因果关系图显示:
[Gesture] → [单个LandmarkViewModel变化] → [单个LandmarkListItemView更新]Environment Updates
环境更新
How Environment Works
环境的工作原理
swift
struct EnvironmentValues {
// Dictionary-like value type
var colorScheme: ColorScheme
var locale: Locale
// ... many more values
}Each view has dependency on entire EnvironmentValues struct via property wrapper.
@Environmentswift
struct EnvironmentValues {
// 类似字典的值类型
var colorScheme: ColorScheme
var locale: Locale
// ... 更多值
}每个视图通过属性包装器依赖整个EnvironmentValues结构体。
@EnvironmentWhat Happens on Environment Change
环境变化时发生的情况
- Any environment value changes (e.g., dark mode enabled)
- All views with dependency notified
@Environment - Each view checks if the specific value it reads changed
- If value changed → View body runs
- If value didn't change → SwiftUI skips running view body (already up-to-date)
Cost: Even when body doesn't run, there's still cost of checking for updates.
- 任何环境值变化(例如启用深色模式)
- 所有带有依赖的视图都会收到通知
@Environment - 每个视图检查它读取的特定值是否变化
- 如果值变化 → View body运行
- 如果值未变化 → SwiftUI跳过运行view body(已处于最新状态)
成本:即使body不运行,检查更新也会产生一定成本。
Environment Update Nodes in Graph
图中的环境更新节点
Two types:
- External Environment — App-level changes from outside SwiftUI (color scheme, accessibility settings)
- EnvironmentWriter — Changes inside SwiftUI via modifier
.environment()
Example:
View1 reads colorScheme:
[External Environment] → [View1 body runs] ✅
View2 reads locale (doesn't read colorScheme):
[External Environment] → [View2 body check] (body doesn't run - dimmed icon)Same update shows as multiple nodes: Hover/click any node for same update → all highlight together.
两种类型:
- External Environment —— 来自SwiftUI外部的应用级变化(配色方案、辅助功能设置)
- EnvironmentWriter —— 通过修饰符在SwiftUI内部进行的变化
.environment()
示例:
View1读取colorScheme:
[External Environment] → [View1 body运行] ✅
View2读取locale(不读取colorScheme):
[External Environment] → [View2 body检查](body不运行 - 灰色图标)同一更新显示为多个节点:悬停/点击任何节点查看同一更新 → 所有相关节点都会高亮。
Environment Performance Warning
环境性能警告
⚠️ AVOID storing frequently-changing values in environment:
swift
// ❌ DON'T DO THIS
struct ContentView: View {
@State private var scrollOffset: CGFloat = 0
var body: some View {
ScrollView {
// Content
}
.environment(\.scrollOffset, scrollOffset) // ❌ Updates on every scroll frame
.onPreferenceChange(ScrollOffsetKey.self) { offset in
scrollOffset = offset
}
}
}Why it's bad:
- Environment change triggers checks in all child views
- Scrolling = 60+ updates/second
- Massive performance hit
✅ Better approach:
swift
// Pass via parameter or @Observable model
struct ContentView: View {
@State private var scrollViewModel = ScrollViewModel()
var body: some View {
ScrollView {
ChildView(scrollViewModel: scrollViewModel) // Direct parameter
}
}
}Environment is great for:
- Color scheme
- Locale
- Accessibility settings
- Other relatively stable values
⚠️ 避免在环境中存储频繁变化的值:
swift
// ❌ 不要这样做
struct ContentView: View {
@State private var scrollOffset: CGFloat = 0
var body: some View {
ScrollView {
// 内容
}
.environment(\.scrollOffset, scrollOffset) // ❌ 每次滚动帧都会更新
.onPreferenceChange(ScrollOffsetKey.self) { offset in
scrollOffset = offset
}
}
}为什么不好:
- 环境变化会触发所有子视图的检查
- 滚动 = 每秒60+次更新
- 会造成巨大的性能损耗
✅ 更好的方法:
swift
// 通过参数或@Observable模型传递
struct ContentView: View {
@State private var scrollViewModel = ScrollViewModel()
var body: some View {
ScrollView {
ChildView(scrollViewModel: scrollViewModel) // 直接传递参数
}
}
}环境适合存储:
- 配色方案
- 区域设置
- 辅助功能设置
- 其他相对稳定的值
Performance Optimization Checklist
性能优化检查清单
Before Profiling
性能分析前
- Build in Release mode (Debug mode has overhead)
- Test on real devices (Simulator performance ≠ real device)
- Update device to latest OS (SwiftUI trace support)
- Identify specific slow interactions to profile
- 以Release模式构建应用(Debug模式有额外开销)
- 在真实设备上测试(模拟器性能 ≠ 真实设备)
- 将设备更新至最新OS版本(支持SwiftUI追踪)
- 确定需要分析的具体缓慢交互场景
During Profiling
性能分析期间
- Use SwiftUI template in Instruments 26
- Focus on Long View Body Updates lane first
- Check Update Groups lane (empty = problem outside SwiftUI)
- Record realistic user workflows (not artificial scenarios)
- Keep profiling sessions short (easier to analyze)
- 使用Instruments 26中的SwiftUI模板
- 优先关注Long View Body Updates通道
- 检查Update Groups通道(无内容 = 问题来自SwiftUI之外)
- 录制真实的用户工作流(而非人工场景)
- 保持性能分析会话简短(更易于分析)
Analyzing Long View Body Updates
分析View Body高耗时更新
- Filter detail pane to "Long View Body Updates"
- Start with red updates, then orange
- Use Time Profiler to find expensive operations
- Look for formatter creation, calculations, I/O
- Check if work can be moved to model layer
- 在详情面板中筛选"Long View Body Updates"
- 先处理红色更新,再处理橙色更新
- 使用Time Profiler查找高耗时操作
- 查找格式化器创建、计算、I/O操作
- 检查是否可将工作移至模型层
Analyzing Unnecessary Updates
分析不必要的更新
- Count view body updates - more than expected?
- Use Cause & Effect Graph to trace data flow
- Check for whole array/collection dependencies
- Verify each view depends only on relevant data
- Avoid frequently-changing environment values
- 统计view body更新次数 - 是否超出预期?
- 使用因果关系图追踪数据流
- 检查是否存在整个数组/集合的依赖
- 验证每个视图仅依赖相关数据
- 避免在环境中存储频繁变化的值
After Optimization
优化后
- Record new trace to verify improvements
- Compare before/after Long View Body Updates counts
- Test on slowest supported device
- Monitor in real-world usage
- Profile regularly during development
- 录制新的追踪数据验证改进效果
- 比较优化前后Long View Body Updates的数量
- 在最慢的支持设备上测试
- 在实际使用中监控性能
- 在开发过程中定期进行性能分析
Production Pressure: When Performance Issues Hit Live
生产环境压力:性能问题在上线后出现
The Problem
问题
When performance issues appear in production, you face competing pressures:
- Engineering manager: "Fix it ASAP"
- VP of Product: "Users have been complaining for hours"
- Deployment window: 6 hours before next App Store review window
- Temptation: Quick fix (add , disable animation, simplify view)
.compositingGroup()
The issue: Quick fixes based on guesses fail 80% of the time and waste your deployment window.
当性能问题出现在生产环境时,你会面临多重压力:
- 工程经理:"尽快修复"
- 产品副总裁:"用户已经抱怨好几个小时了"
- 发布窗口:距离下一个App Store审核窗口还有6小时
- 诱惑:快速修复(添加、禁用动画、简化视图)
.compositingGroup()
问题:基于猜测的快速修复有80%的概率失败,并且会浪费你的发布窗口。
Red Flags — Resist These Pressure Tactics
危险信号 —— 抵制这些压力下的错误做法
If you hear ANY of these under deadline pressure, STOP and use SwiftUI Instrument:
- ❌ "Just add .compositingGroup()" – Without profiling, you don't know if this helps
- ❌ "We can roll back if it doesn't work" – App Store review takes 24 hours; rollback isn't fast
- ❌ "Other apps use this pattern" – Doesn't mean it solves YOUR specific problem
- ❌ "Users will accept degradation for now" – Once shipped, you're committed for 24 hours
- ❌ "We don't have time to profile" – You have less time if you guess wrong
如果在截止日期压力下听到以下任何建议,停止并使用SwiftUI Instrument:
- ❌ "直接添加.compositingGroup()到TabView" – 没有性能分析,你不知道这是否有用
- ❌ "如果没用我们可以回滚" – App Store审核需要24小时;回滚并不快
- ❌ "其他应用都用这个模式" – 不代表它能解决你的具体问题
- ❌ "用户现在能接受性能下降" – 一旦发布,你要等24小时才能再次发布
- ❌ "我们没时间做性能分析" – 如果你猜错了,花费的时间会更多
One SwiftUI Instrument Recording (30-Minute Protocol)
一次SwiftUI Instrument录制(30分钟流程)
Under production pressure, one good diagnostic recording beats random fixes:
Time Budget:
- Build in Release mode: 5 min
- Launch and interact to trigger sluggishness: 3 min
- Record SwiftUI Instrument trace: 5 min
- Review Long View Body Updates lane: 5 min
- Check Cause & Effect Graph: 5 min
- Identify specific expensive view: 2 min
Total: 25 minutes to know EXACTLY what's slow
Then:
- Apply targeted fix (15-30 min)
- Test in Instruments again (5 min)
- Ship with confidence
Total time: 1 hour 15 minutes for diagnosis + fix, leaving 4+ hours for edge case testing.
在生产环境压力下,一次好的诊断录制胜过随机尝试修复:
时间预算:
- 以Release模式构建:5分钟
- 启动应用并操作触发卡顿:3分钟
- 录制SwiftUI Instrument追踪数据:5分钟
- 查看Long View Body Updates通道:5分钟
- 检查因果关系图:5分钟
- 识别具体的高耗时视图:2分钟
总计:25分钟即可准确知道哪里慢
然后:
- 应用针对性修复(15-30分钟)
- 再次用Instruments测试(5分钟)
- 自信地发布
总耗时:1小时15分钟完成诊断+修复,留下4+小时进行边缘情况测试。
Comparing Time Costs
时间成本对比
Option A: Guess and Pray
选项A:猜测并祈祷
- Time to implement: 30 min
- Time to deploy: 20 min
- Time to learn it failed: 24 hours (next App Store review)
- Total delay: 24 hours minimum
- User suffering: Continues through deployment window
- 实现时间:30分钟
- 发布时间:20分钟
- 发现修复失败的时间:24小时(下一次App Store审核)
- 总延迟:至少24小时
- 用户困扰:持续到发布窗口结束
Option B: One SwiftUI Instrument Recording
选项B:一次SwiftUI Instrument录制
- Time to diagnose: 25 min
- Time to apply targeted fix: 20 min
- Time to verify: 5 min
- Time to deploy: 20 min
- Total time: 1.5 hours
- User suffering: Stopped after 2 hours instead of 26+ hours
Time cost of being wrong:
- A: 24-hour delay + reputational damage + users suffering
- B: 1.5 hours + you know the actual problem + confidence in the fix
- 诊断时间:25分钟
- 应用针对性修复时间:20分钟
- 验证时间:5分钟
- 发布时间:20分钟
- 总耗时:1.5小时
- 用户困扰:2小时后解决,而非26+小时
猜错的时间成本:
- A:24小时延迟 + 声誉损失 + 用户持续困扰
- B:1.5小时 + 你知道实际问题 + 对修复有信心
Real-World Example: Tab Transition Sluggishness
真实案例:Tab切换卡顿
Pressure scenario:
- iOS 26 build shipped
- Users report "sluggish tab transitions"
- VP asking for updates every hour
- 6 hours until deployment window closes
Bad approach (Option A):
Junior suggests: "Add .compositingGroup() to TabView"
You: "Sure, let's try it"
Result: Ships without profiling
Outcome: Doesn't fix issue (compositing wasn't the problem)
Next: 24 hours until next deploy window
VP update: "Users still complaining"Good approach (Option B):
"Running one SwiftUI Instrument recording of tab transition"
[25 minutes later]
"SwiftUI Instrument shows Long View Body Updates in ProductGridView during transition.
Cause & Effect Graph shows ProductList rebuilding entire grid unnecessarily.
Applying view identity fix (`.id()`) to prevent unnecessary updates"
[30 minutes to implement and test]
"Deployed at 1.5 hours. Verified with Instruments. Tab transitions now smooth."压力场景:
- iOS 26版本已发布
- 用户反馈"Tab切换卡顿"
- 副总裁每小时询问更新
- 距离发布窗口关闭还有6小时
错误做法(选项A):
初级开发者建议:"给TabView添加.compositingGroup()"
你:"好的,我们试试"
结果:未做性能分析就发布
结果:问题未解决(合成不是问题所在)
下一步:24小时后才能再次发布
副总裁更新:"用户仍在抱怨"正确做法(选项B):
"正在录制Tab切换的SwiftUI Instrument追踪数据"
[25分钟后]
"SwiftUI Instrument显示Tab切换期间ProductGridView的View Body更新耗时过长。
因果关系图显示ProductList不必要地重建了整个网格。
应用视图标识修复(`.id()`)以避免不必要的更新"
[30分钟实现并测试]
"1.5小时后发布。已用Instruments验证。Tab切换现在流畅了。"When to Accept the Pressure (And Still be Right)
何时可以接受压力(且仍能做对)
Sometimes managers are right to push for speed. Accept the pressure IF:
- You've run ONE SwiftUI Instrument recording (25 minutes)
- You know what specific view/operation is expensive
- You have a targeted fix, not a guess
- You've verified the fix in Instruments before shipping
- You're shipping WITH profiling data, not hoping it works
Document your decision:
Slack to VP + team:
"Completed diagnostic: ProductGridView rebuilding unnecessarily during
tab transitions (confirmed in SwiftUI Instrument, Long View Body Updates).
Applied view identity fix. Verified in Instruments - transitions now 16.67ms.
Deploying now."This shows:
- You diagnosed (not guessed)
- You solved the right problem
- You verified the fix
- You're shipping with confidence
有时管理者催促速度是对的。如果满足以下条件,可以接受压力:
- 你已经运行了一次SwiftUI Instrument录制(25分钟)
- 你知道哪个视图/操作耗时高
- 你有针对性的修复方案,而非猜测
- 你已用Instruments验证修复有效后再发布
- 你是带着性能分析数据发布,而非寄希望于修复有效
记录你的决策:
发送给副总裁+团队的Slack消息:
"已完成诊断:Tab切换期间ProductGridView不必要地重建(已在SwiftUI Instrument的Long View Body Updates中确认)。
已应用视图标识修复。经Instruments验证 - 切换耗时现在为16.67ms。
正在发布。"这表明:
- 你进行了诊断(而非猜测)
- 你解决了正确的问题
- 你验证了修复效果
- 你对发布有信心
If You Still Get It Wrong After Profiling
如果做了性能分析后仍然修复失败
Honest admission:
"SwiftUI Instrument showed ProductGridView was the bottleneck.
Applied view identity fix, but performance didn't improve as expected.
Root cause is deeper than expected. Requiring architectural change.
Shipping animation disable (.animation(nil) on TabView) as mitigation.
Proper fix queued for next release cycle."This is different from guessing:
- You have evidence of the root cause
- You understand why the quick fix didn't work
- You're buying time with a known mitigation
- You're committed to proper fix next cycle
坦诚说明:
"SwiftUI Instrument显示ProductGridView是瓶颈。
已应用视图标识修复,但性能未如预期提升。
根本原因比预期更深,需要架构变更。
暂时发布禁用动画的版本(TabView添加`.animation(nil)`)作为缓解方案。
完整修复已排入下一版本周期。"这与猜测不同:
- 你有证据证明根本原因
- 你理解为什么快速修复无效
- 你用已知的缓解方案争取时间
- 你承诺在下一版本进行完整修复
Decision Framework Under Pressure
压力下的决策框架
Before shipping ANY fix
在发布任何修复之前
| Question | Answer Yes? | Action |
|---|---|---|
| Have you run SwiftUI Instrument? | No | STOP - 25 min diagnostic |
| Do you know which view is expensive? | No | STOP - review Cause & Effect Graph |
| Can you explain in one sentence why the fix helps? | No | STOP - you're guessing |
| Have you verified the fix in Instruments? | No | STOP - test before shipping |
| Did you consider simpler explanations? | No | STOP - check documentation first |
Answer YES to all five → Ship with confidence
| 问题 | 答案是? | 行动 |
|---|---|---|
| 你是否运行了SwiftUI Instrument? | 否 | 停止 - 进行25分钟诊断 |
| 你知道哪个视图耗时高吗? | 否 | 停止 - 查看因果关系图 |
| 你能用一句话解释修复的作用吗? | 否 | 停止 - 你在猜测 |
| 你已用Instruments验证修复效果吗? | 否 | 停止 - 发布前测试 |
| 你考虑过更简单的解释吗? | 否 | 停止 - 先查看文档 |
全部回答是 → 自信地发布
Common Patterns & Solutions
常见模式与解决方案
Pattern 1: List Item Dependencies
模式1:列表项依赖
Problem: Updating one item updates entire list
Solution: Per-item view models with granular dependencies
swift
// ❌ Shared dependency
@Observable
class ListViewModel {
var items: [Item] // All views depend on whole array
}
// ✅ Granular dependencies
@Observable
class ListViewModel {
private(set) var itemViewModels: [Item.ID: ItemViewModel]
}
@Observable
class ItemViewModel {
var item: Item // Each view depends only on its item
}问题:更新一个项导致整个列表更新
解决方案:为每个项设置视图模型,实现细粒度依赖
swift
// ❌ 共享依赖
@Observable
class ListViewModel {
var items: [Item] // 所有视图依赖整个数组
}
// ✅ 细粒度依赖
@Observable
class ListViewModel {
private(set) var itemViewModels: [Item.ID: ItemViewModel]
}
@Observable
class ItemViewModel {
var item: Item // 每个视图仅依赖自己的项
}Pattern 2: Computed Properties in View Bodies
模式2:View Body中的计算属性
Problem: Expensive computation runs every render
Solution: Move to model, cache result
swift
// ❌ Compute in view
struct MyView: View {
let data: [Int]
var body: some View {
Text("\(data.sorted().last ?? 0)") // Sorts every render
}
}
// ✅ Compute in model
@Observable
class ViewModel {
var data: [Int] {
didSet {
maxValue = data.max() ?? 0 // Compute once when data changes
}
}
private(set) var maxValue: Int = 0
}
struct MyView: View {
@Environment(ViewModel.self) private var viewModel
var body: some View {
Text("\(viewModel.maxValue)") // Just read cached value
}
}问题:高耗时计算在每次渲染时都运行
解决方案:移至模型层并缓存结果
swift
// ❌ 在视图中计算
struct MyView: View {
let data: [Int]
var body: some View {
Text("\(data.sorted().last ?? 0)") // 每次渲染都排序
}
}
// ✅ 在模型中计算
@Observable
class ViewModel {
var data: [Int] {
didSet {
maxValue = data.max() ?? 0 // 数据变化时仅计算一次
}
}
private(set) var maxValue: Int = 0
}
struct MyView: View {
@Environment(ViewModel.self) private var viewModel
var body: some View {
Text("\(viewModel.maxValue)") // 仅读取缓存值
}
}Pattern 3: Formatter Reuse
模式3:格式化器复用
Problem: Creating formatters repeatedly
Solution: Create once, reuse
swift
// ❌ Create every time
var body: some View {
let formatter = DateFormatter()
formatter.dateStyle = .short
Text(formatter.string(from: date))
}
// ✅ Reuse formatter
class Formatters {
static let shortDate: DateFormatter = {
let f = DateFormatter()
f.dateStyle = .short
return f
}()
}
var body: some View {
Text(Formatters.shortDate.string(from: date))
}问题:重复创建格式化器
解决方案:创建一次并复用
swift
// ❌ 每次都创建
var body: some View {
let formatter = DateFormatter()
formatter.dateStyle = .short
Text(formatter.string(from: date))
}
// ✅ 复用格式化器
class Formatters {
static let shortDate: DateFormatter = {
let f = DateFormatter()
f.dateStyle = .short
return f
}()
}
var body: some View {
Text(Formatters.shortDate.string(from: date))
}Pattern 4: Environment for Stable Values Only
模式4:仅在环境中存储稳定值
Problem: Rapidly-changing environment values
Solution: Use direct parameters or models
swift
// ❌ Frequently changing in environment
.environment(\.scrollPosition, scrollPosition) // 60+ updates/second
// ✅ Direct parameter or model
ChildView(scrollPosition: scrollPosition)问题:环境中存储频繁变化的值
解决方案:使用直接参数或模型
swift
// ❌ 环境中存储频繁变化的值
.environment(\.scrollPosition, scrollPosition) // 每秒60+次更新
// ✅ 直接传递参数或使用模型
ChildView(scrollPosition: scrollPosition)iOS 26 Performance Improvements
iOS 26性能提升
Automatic improvements when building with Xcode 26 (no code changes needed):
使用Xcode 26构建时自动获得的提升(无需修改代码):
Lists
列表
- Update up to 16× faster
- Large lists on macOS load 6× faster
- 更新速度最高提升16倍
- macOS上的大型列表加载速度提升6倍
SwiftUI Instrument
SwiftUI Instrument
- Next-generation performance analysis
- Captures detailed cause-and-effect information
- Makes it easier than ever to understand when and why views update
- 新一代性能分析功能
- 捕获详细的因果关系信息
- 比以往更容易理解视图何时以及为何更新
Debugging Performance Issues
调试性能问题
Step-by-Step Process
分步流程
- Reproduce issue — Identify specific slow interaction
- Profile with Instruments — SwiftUI template
- Check Update Groups lane — SwiftUI doing work when slow?
- Identify problem type:
- Long View Body Updates? → Section on Long Updates
- Too many updates? → Section on Unnecessary Updates
- Use Time Profiler for long updates (find expensive operation)
- Use Cause & Effect Graph for unnecessary updates (find dependency issue)
- Implement fix
- Verify with new trace
- 重现问题 —— 确定具体的缓慢交互场景
- 用Instruments分析 —— 使用SwiftUI模板
- 检查Update Groups通道 —— 缓慢时SwiftUI是否在工作?
- 确定问题类型:
- View Body更新耗时过长? → 查看高耗时更新章节
- 更新次数过多? → 查看不必要更新章节
- 对高耗时更新使用Time Profiler(查找高耗时操作)
- 对不必要更新使用因果关系图(查找依赖问题)
- 实施修复
- 用新的追踪数据验证
When SwiftUI Isn't the Problem
当问题不在SwiftUI时
Update Groups lane empty during performance issue?
性能问题出现时Update Groups通道为空?
Problem likely elsewhere:
- Network requests
- Background processing
- Image loading
- Database queries
- Third-party frameworks
Next steps:
Real-World Impact
实际影响
Example: Landmarks App (from WWDC 2025)
示例:Landmarks应用(来自WWDC 2025)
Before optimization:
- Every favorite button tap updated ALL visible landmark views
- Each view recreated formatters for distance calculation
- Scrolling felt janky
After optimization:
- Only tapped view updates (granular view models)
- Formatters created once, strings cached
- Smooth 60fps scrolling
Improvements:
- 100+ unnecessary view updates → 1 update per action
- Milliseconds saved per view × dozens of views = significant improvement
- Eliminated long view body updates entirely
优化前:
- 每次点击收藏按钮都会更新所有可见的地标视图
- 每个视图都重新创建距离计算的格式化器
- 滚动感觉卡顿
优化后:
- 仅被点击的视图更新(细粒度视图模型)
- 格式化器仅创建一次,字符串被缓存
- 流畅的60fps滚动
提升效果:
- 100+次不必要的视图更新 → 每次操作仅1次更新
- 每个视图节省数毫秒 × 数十个视图 = 显著提升
- 完全消除了View Body高耗时更新问题
Resources
资源
WWDC: 2025-306
Docs: /xcode/understanding-hitches-in-your-app, /xcode/analyzing-hangs-in-your-app, /xcode/optimizing-your-app-s-performance
Skills: axiom-swiftui-debugging-diag, axiom-swiftui-debugging, axiom-memory-debugging, axiom-xcode-debugging
WWDC: 2025-306
文档: /xcode/understanding-hitches-in-your-app, /xcode/analyzing-hangs-in-your-app, /xcode/optimizing-your-app-s-performance
技能: axiom-swiftui-debugging-diag, axiom-swiftui-debugging, axiom-memory-debugging, axiom-xcode-debugging
Key Takeaways
关键要点
- Fast view bodies — Keep them quick so SwiftUI has time to get UI on screen without delay
- Update only when needed — Design data flow to update views only when necessary
- Careful with environment — Don't store frequently-changing values
- Profile early and often — Use Instruments during development, not just when problems arise
- Greatest takeaway: Ensure your view bodies update quickly and only when needed to achieve great SwiftUI performance
Xcode: 26+
Platforms: iOS 26+, iPadOS 26+, macOS Tahoe+, axiom-visionOS 3+
History: See git log for changes
- 快速的view body —— 保持view body执行迅速,让SwiftUI有时间在截止时间前将UI显示在屏幕上
- 仅在必要时更新 —— 设计数据流,仅在必要时更新视图
- 谨慎使用环境 —— 不要存储频繁变化的值
- 尽早并定期进行性能分析 —— 在开发过程中使用Instruments,而不仅仅是出现问题时
- 最重要的要点:确保你的view body更新迅速且仅在必要时更新,以实现出色的SwiftUI性能
Xcode: 26+
平台: iOS 26+, iPadOS 26+, macOS Tahoe+, axiom-visionOS 3+
历史: 查看git log了解变更