资讯动态

【SwiftUI娓娓道来】第8课:让App跑得更快——SwiftUI 性能调优与调试

发布时间:2026/9/28 11:42:05 来源:尧图企业网站定制
开篇能跑和跑得好是两回事前七课我们已经走完了 SwiftUI 的完整学习路径。第1课认识了积木第2课学会了布局第3课让界面活了起来第4课让数据留了下来第5课让数据流动了起来第6课让代码立了起来第7课让 App 走了出去。现在你的 App 功能齐全、架构清晰、界面漂亮甚至已经上架了。你觉得自己已经做到了。但是用户打开 App滑动列表时感觉有点卡。打开一个详情页要等一秒才显示。滚动到列表底部内存占用越来越高。电池消耗得特别快手机发烫。这些问题用户能感觉到但说不清楚。他们不会告诉你“你的 View body 计算耗时太长了”或者“你的环境写入导致了过多的视图失效”。他们只会说一句“这个 App 有点卡。”能跑和跑得好是两回事。你可以把性能优化想象成给一辆车做调校。发动机能转车能开这是基本要求。但好的调校能让车加速更快、油耗更低、操控更稳。性能优化就是给你的 App 做调校让它不只是“能用”而是“好用”。这一课我们就来聊 SwiftUI 的性能调优与调试。我们会讲理解 SwiftUI 的性能模型用 Instruments 找到性能瓶颈长更新与频繁更新的诊断与修复视图体复杂度、状态传播、环境写入的优化内存泄漏的检测与修复调试工具与技巧性能优化的最佳实践。这一课会很长但不会难。因为性能优化的本质其实就是找到瓶颈然后消除它。理解了这个本质剩下的就是学会用工具、掌握技巧、积累经验。好我们开始。一、理解 SwiftUI 的性能模型1.1 SwiftUI 的渲染流程要优化 SwiftUI 的性能首先要理解它是怎么工作的。SwiftUI 采用声明式的方式来构建用户界面。你描述 App 的 UI 以及它如何依赖 App 的数据和环境。SwiftUI 计算代表 UI 的视图并根据用户操作和视图依赖的变化更新 UI。渲染流程大致是这样的状态变化某个State、Observable属性或环境值发生了变化重新计算 bodySwiftUI 会重新执行受影响 View 的body生成新视图树body返回一个新的视图描述Diff 比较SwiftUI 把新视图树和旧视图树做比较找出差异更新渲染只更新有差异的部分。这个过程非常高效但前提是body计算要快状态辐射范围要小视图身份要稳定。如果body里做了很重的计算或者一个状态变化导致大量 View 重新计算或者视图身份不稳定导致 SwiftUI 无法正确复用性能就会下降。苹果官方文档指出视图体运行时间过长或者视图更新过于频繁都会降低整体系统效率。1.2 性能问题的两种类型SwiftUI 的性能问题通常分为两类它们需要不同的修复方法长更新Long Updates一次更新耗时太长。这通常来自昂贵的body计算、昂贵的布局计算或者嵌入的平台视图UIKit/AppKit工作。频繁更新Frequent Updates许多小更新频繁发生。这通常来自依赖扇出、嘈杂的可观察状态或者几何和定时器信号让比预期更多的视图树失效。苹果官方文档明确指出需要识别和解决长时间运行的视图更新并减少更新的频率。这两类问题用户都能感觉到但修复方式完全不同。长更新要“减负”频繁更新要“限流”。1.3 性能问题的三个信号卡顿Hitches动画暂停、跳帧滚动不流畅挂起HangsApp 无响应界面冻结高耗电Battery DrainCPU 持续高负载电池消耗快。系统要求视图在渲染下一帧之前完成更新。如果视图没有及时完成更新就会导致 UI 卡顿。在 WWDC26 的 Power and Performance Group Lab 中苹果工程师特别讨论了 SwiftUI 中影响功耗和性能的主要因素其中视图过度失效view over-invalidation被反复提及——许多开发者没有意识到他们的 App 在不知不觉中消耗了大量电量。1.4 一个关键指标View Body 评估次数SwiftUI Instrument 会告诉你每个 View 的body被评估了多少次。如果一个 View 的body被评估了远超预期的次数说明它被不必要的更新触发了。比如一个列表行用户没有滚动但它的body被评估了 50 次。这说明某个状态变化在不停地触发这个 View 的更新。找到这个状态限制它的辐射范围性能就会改善。二、用 Instruments 找到性能瓶颈2.1 性能优化的黄金法则先测量再优化很多开发者的第一反应是“我觉得这里慢我优化这里”。但你的直觉往往是错的。你觉得慢的地方可能只占 5% 的时间真正慢的地方你可能根本没注意到。苹果官方文档明确建议“不要猜测性能问题。使用 Instruments 来识别视图体执行、重新渲染和布局中的实际瓶颈。”性能优化的黄金法则先用 Instruments 测量找到真正的瓶颈再优化。2.2 SwiftUI Instrument专为 SwiftUI 设计的性能工具Xcode 26 引入了全新的SwiftUI Instrument这是分析 SwiftUI 性能最直接的工具。要开始使用需要安装 Xcode 26并将设备更新到支持记录 SwiftUI 跟踪数据的操作系统版本。使用步骤在 Release 模式下分析。Debug 模式有额外的调试代码性能数据不准确。在 Xcode 中选择 Product ProfileCmdI选择SwiftUI 模板Instruments 26 中提供运行你的 App反复重现同一个交互。如果你无法按需重现精确的交互跟踪数据会很难解读停止记录查看 SwiftUI 轨道和 Time Profiler 轨道。2.3 如何解读 SwiftUI Instrument 的结果打开 SwiftUI Instrument 后你会看到几个关键的轨道Update Groups显示 SwiftUI 何时在为你的 App 计算更新。如果这个轨道是空的但 CPU 使用率却在飙升说明问题不在 SwiftUI应该去看 Time Profiler 或 CPU 分析工具Long View Body Updates橙色线表示超过 500 微秒的视图体计算红色线表示超过 1000 微秒的计算。这些通常直接指向你可以简化或移出渲染路径的代码Long Platform View Updates当 SwiftUI 承载 UIKit/AppKit 内容时使用。如果这个轨道很热检查UIViewRepresentable、UIViewControllerRepresentable以及嵌入平台视图的列表行内容Other Long Updates捕获非视图体的 SwiftUI 工作如几何布局和文本布局计算。关键操作不要从总运行时间开始而要从红色的更新窗口开始。放大一个耗时较长的更新切换到 Time Profiler 轨道查看是哪些代码帧在消耗时间。常见发现包括视图体中的格式化、视图体中的数组变换、滚动中的图片解码、平台视图更新每次做太多工作。2.4 用 Cause and Effect Graph 理解数据流SwiftUI Instrument 还提供了一个Cause and Effect Graph因果关系图它直观地展示了数据如何在 App 中流动帮助你确保视图不会在不必要的时候被更新。这个图会区分几种更新类型External Environment Updates来自系统级别的环境变化比如设备切换到深色模式EnvironmentWriter Updates来自 App 内部通过.environment修饰符写入的值。图中一个视图的 body 没有执行但被标记为更新的情况会用一个淡化的图标表示。即使视图体不需要在环境更新后执行仍然有与检查视图感兴趣的值相关的成本。如果 App 有很多视图读取环境数据这个成本会迅速增加。2.5 Hangs 和 Hitches InstrumentXcode 提供了Hangs Instrument和Hitches Instrument专门用于检测界面的无响应和卡顿。Hitches 时间线会报告 App 没有及时准备视图更新、导致系统无法按时渲染到屏幕的情况。2.6 一个完整的性能分析流程明确目标你想优化什么滚动卡顿启动速度内存占用建立基线先在当前版本上测量记录数据用 Instruments 分析在 Release 模式下选择 SwiftUI 模板找到瓶颈修复问题针对瓶颈优化代码再次测量对比优化前后的数据。每次只改一件事然后重新记录。如果你一次改了三处就无法知道哪一处起了作用。三、长更新的诊断与修复3.1 长更新的常见原因昂贵的 body 计算在body里做排序、过滤、日期格式化、字符串拼接昂贵的布局计算深层嵌套的容器、GeometryReader滥用嵌入的平台视图工作UIViewRepresentable每次更新做太多工作。3.2 修复策略把重计算移出 body// ❌ 不好每次 body 重算都排序varbody:someView{letsorteditems.sorted{$0.date$1.date}List(sorted){iteminItemRow(item:item)}}// ✅ 好在 ViewModel 里预计算并缓存ObservablefinalclassItemListViewModel{private(set)varsortedItems:[Item][]funcupdateItems(_newItems:[Item]){sortedItemsnewItems.sorted{$0.date$1.date}}}日期格式化是常见的性能杀手。每次创建DateFormatter都很昂贵。应该在 ViewModel 或工具类中复用同一个实例extensionDateFormatter{staticletshortDate:DateFormatter{letformatterDateFormatter()formatter.dateStyle.shortreturnformatter}()}在模型中预格式化字符串而不是每次调用格式化器// ❌ 不好在 body 中直接使用格式化器Text(DateFormatter.localizedString(from:message.timestamp,dateStyle:.short,timeStyle:.short))// ✅ 好在模型中预格式化structMessageRow:View{letmessage:Messagevarbody:someView{Text(message.formattedTimestamp)// 字符串已预格式化}}正确的修复通常是架构层面的——把计算移到模型层或 ViewModel 中而不是对同一个body代码进行微优化。3.3 视图体复杂度控制在 10 个节点以内这是一个非常实用的经验法则当一个视图体包含超过约 10 个直接子节点时SwiftUI 无法高效地隔离哪个子树发生了变化导致任何属性变化都会重新评估整个视图体。// ❌ 不好视图体超过 15 个内联节点任何变化都会重算整个 bodystructActivityFeedView:View{StatevarviewModel:ActivityFeedViewModelvarbody:someView{NavigationStack{VStack{Picker(筛选,selection:$viewModel.filter){/* ... */}.pickerStyle(.segmented)ifviewModel.filteredActivities.isEmpty{VStack(spacing:12){Image(systemName:tray).font(.system(size:48))Text(没有活动).font(.headline)Text(试试改变筛选条件).font(.subheadline)}}else{List(viewModel.filteredActivities){activityinHStack{/* 行内容 */}}}}}}}// ✅ 好拆分为独立子视图每个子视图创建独立的 diffing 检查点structActivityFeedView:View{StatevarviewModel:ActivityFeedViewModelvarbody:someView{NavigationStack{VStack{ActivityFilterPicker(selection:$viewModel.filter)ifviewModel.filteredActivities.isEmpty{ActivityEmptyState()}else{ActivityList(activities:viewModel.filteredActivities)}}}}}每个子视图创建一个 diffing 检查点当它的输入没有变化时SwiftUI 可以跳过整个子树的计算。3.4 图片解码和缩放症状滚动包含图片的列表时卡顿内存占用高。修复下载时就请求合适尺寸的图片、在后台线程解码图片、使用图片缓存。funcloadImage(from url:URL)async-UIImage?{awaitTask.detached{guardletdatatry?Data(contentsOf:url),letimageUIImage(data:data)else{returnnil}returnimage}.value}四、频繁更新的诊断与修复4.1 频繁更新的常见原因依赖扇出一个状态变化触发大量 View 更新嘈杂的可观察状态Observable对象持有太多不相关的属性几何和定时器信号GeometryReader或 Timer 让比预期更多的视图树失效状态定义在太高的层级根节点持有所有状态环境写入向环境中写入频繁变化的值可能导致读取任意环境键的视图被无效化。4.2 环境写入的隐藏代价这是一个很多开发者不知道的重要细节。苹果工程师在开发者论坛中澄清向环境写入一个值不仅仅影响读取该键的视图而是会让该环境修饰符下所有读取任意环境键的子视图都受到影响。视图体只有在实际使用了该键对应的环境值并且该值发生变化时才会重新执行。但在 SwiftUI 中更新并不总是导致视图体再次运行仍然有与之相关的成本。在 Instruments 中这些更新被标记为“skipped”跳过。演示表明所有跳过更新的累计成本是显著的即使对应的视图体没有运行。这意味着环境应该主要用于稳定的、长生命周期的对象而不是频繁变化的值因为写入可能导致远超大多数开发者想象的视图无效化。如果 App 有很多视图读取环境数据成本会迅速增加。// ❌ 不好把频繁变化的值放到环境里.environment(\.selectedTabIndex,selectedTabIndex)// ✅ 好把频繁变化的值包装在 Observable 对象中ObservablefinalclassSelectedTab{varindex:Int0}.environment(selectedTab)// 传递对象引用只有读取 index 的视图才会更新4.3 把状态下移到需要它的最近的共同祖先// ❌ 不好所有状态都在根 ViewstructContentView:View{StateprivatevarsearchTextStateprivatevaritems:[Item][]StateprivatevarselectedTab0StateprivatevarisShowingSheetfalse// 任何一个状态变化整个 ContentView 都重算}// ✅ 好状态下移到需要的子 ViewstructContentView:View{varbody:someView{TabView{SearchView()// 搜索状态在 SearchView 内部ItemsView()// 列表状态在 ItemsView 内部}}}4.4 用_printChanges()调试SwiftUI 提供了一个调试工具_printChanges()可以打印出 View 的body为什么被重新评估。这是一个 debug-only 的 API不应该出现在生产版本中。varbody:someView{let_Self._printChanges()List(items){iteminItemRow(item:item)}}控制台会输出类似ContentView: _searchText changed.的信息帮你快速定位是哪个状态触发了更新。调用_printChanges()会重新评估视图体所以如果你在控制台看到大量输出说明视图体被频繁触发了。五、列表性能优化5.1 稳定 ID列表性能的基石绝对不要用UUID()作为ForEach的 id。优先使用模型中已存在的 ID比如数据库主键、服务器 ID 或稳定的 slug。如果没有在创建模型时生成一次——不要在视图内生成。ID 必须满足三个条件稳定、唯一、廉价可哈希。如果使用数组索引作为 ID当列表项被插入或删除时所有后续项的 ID 都会变化导致大量不必要的重建。5.2 使用 Lazy 容器VStack和HStack会立即创建所有子视图包括屏幕外的。对于任何超过约 20 个项目的列表或者大小未知的动态内容都应该使用ScrollView中的LazyVStack/LazyHStack。懒加载容器只在视图滚动到可见区域时才创建它们将内存使用和初始渲染时间从 O(N) 降低到 O(可见数量)。List和LazyVStack的选择经典的表格/集合界面设置、收件箱、订单用List它围绕系统优化和行复用设计在大数据集上内存效率通常更好。需要自定义布局和混合内容时用ScrollView LazyVStack但要注意它对视图身份和频繁更新更敏感。5.3 懒加载容器中的条件语句陷阱这是一个鲜为人知的性能陷阱在懒加载容器中如果视图的顶层有一个if条件语句SwiftUI 无法确定该视图是否应该存在因此会保留它以便检测条件状态的变化。这意味着即使视图滚出屏幕它也可能不会被释放。// ❌ 不好在 LazyVStack 的行中顶层使用 ifLazyVStack{ForEach(items){iteminifitem.isVisible{ItemRow(item:item)}}}// ✅ 好把条件包装在容器中始终产生一个视图LazyVStack{ForEach(items){iteminVStack{ifitem.isVisible{ItemRow(item:item)}}}}当你把条件包装在VStack这样的单目容器中时它始终解析为一个视图SwiftUI 就能走懒加载容器的快速路径。5.4 减少每行工作量在行视图里做日期格式化、过滤、调整图片大小或者在行内执行网络/磁盘工作都是常见的性能问题。把这类工作移到模型层或 ViewModel 中在数据变化时预计算而不是在每次行渲染时计算。六、内存泄漏的检测与修复6.1 什么是内存泄漏内存泄漏是指对象不再需要了但因为某些引用关系它无法被释放一直占用内存。在 SwiftUI 中常见的内存泄漏来源循环引用闭包捕获了self而self又持有这个闭包懒加载容器中的 ViewModel 泄漏LazyVStack或List保留了大量 ViewModel定时器和观察者没有在 View 消失时取消。6.2 懒加载容器中的 ViewModel 泄漏这是一个常见但容易被忽略的问题。在使用LazyVStack或List时如果每个行都有一个 ViewModel而这些 ViewModel 被 SwiftUI 内部持有就会导致内存增长。即使用户移除了数据ViewModel 也不会被释放。社区中已经确认解决这个问题的方法是使用一个独立的类来持有列表项并在行视图中使用weak var viewModel。// ✅ 好列表持有数据行视图弱引用 ViewModelObservablefinalclassListViewModel{varitems:[CellViewModel][]funcremoveLast(){itemsitems.dropLast()}}structCellView:View{weakvarviewModel:CellViewModel?varbody:someView{ifletviewModel{Text(viewModel.title)}}}6.3 用 Debug Memory Graph 检测泄漏Xcode 的Debug Memory Graph是检测内存泄漏最直观的工具。运行 AppDebug 模式操作怀疑有泄漏的功能点击调试栏的 Debug Memory Graph 按钮在左侧面板查看对象列表点击对象查看引用关系图。如果看到循环引用那就是泄漏。6.4 常见的循环引用场景// ❌ 泄漏classViewModel{varonUpdate:(()-Void)?funcsetup(){onUpdate{self.load()// 捕获 self形成循环}}}// ✅ 修复classViewModel{varonUpdate:(()-Void)?funcsetup(){onUpdate{[weakself]inself?.load()}}}6.5 内存管理的三条原则闭包中捕获 self 时考虑用[weak self]除非你确定不会形成循环在 View 消失时取消定时器、观察者、网络请求定期用 Instruments 检查内存不要等到用户抱怨才去查。七、调试工具与技巧7.1 Xcode 的 Debug 工具Debug View Hierarchy暂停 App查看当前的视图层级。你可以看到每个 View 的边界、修饰符、状态。Environment Overrides在调试栏中可以实时切换动态字体、深色模式、无障碍设置观察界面变化。Debug Memory Graph查看对象的引用关系检测循环引用。7.2 在 Preview 中测试不同配置#Preview(最大字号){ContentView().environment(\.dynamicTypeSize,.accessibility5)}#Preview(深色模式){ContentView().preferredColorScheme(.dark)}用不同的 Preview 配置测试不同场景可以在开发阶段就发现性能问题。7.3 用 Signposts 标记性能关键路径Signposts 是 OSLog 的一个功能可以在 Instruments 中标记你的代码执行importosletsignposterOSSignposter(subsystem:com.myapp,category:performance)funcloadData()async{letstatesignposter.beginInterval(LoadData)defer{signposter.endInterval(LoadData,state)}// 加载数据}在 Instruments 中你会看到LoadData的时间段可以精确测量它的耗时。7.4 关于 AnyView 的性能代价苹果工程师在 WWDC26 QA 中讨论了AnyView作为类型擦除工具的性能开销。AnyView会破坏 SwiftUI 的视图身份使得 Diff 算法无法正确追踪视图的变化导致不必要的重建。在懒加载容器中尤其需要注意因为它可能产生多个子视图这会影响懒加载容器的快速路径。// ❌ 不好AnyView 在懒加载容器中可能导致性能问题ForEach(items){iteminAnyView(ItemRow(item:item))}// ✅ 好直接用具体类型ForEach(items){iteminItemRow(item:item)}7.5 2026 年 SwiftUI 的性能新能力WWDC26 为 SwiftUI 带来了一批直接改善性能的新 API新的 Document 协议支持直接磁盘访问和快照差异比对用于构建高性能的文档类 App列表、网格、分区的内容重排新增了reorderable()修饰符和reorderContainerAPI嵌套布局调整大小速度提升至两倍AsyncImage 自动 HTTP 缓存不再需要手动实现图片缓存Observable 类型的惰性状态初始化减少初始化时的开销任意视图上的轻扫操作以前只有List的行才有.swipeActions现在任意容器都可以用。八、性能优化的最佳实践8.1 优化原则先测量再优化。不要凭感觉优化。用 Instruments 找到真正的瓶颈。从最大的瓶颈开始。如果body计算占了 80% 的时间优化图片缓存可能只占 5%。先解决大的。优化后再次测量。确认优化有效并检查是否引入了新问题。在优化前后都进行性能分析验证改进效果。不要过早优化。先把功能做对再做快。过早优化会增加代码复杂度可能得不偿失。8.2 代码层面的优化清单减少 body 中的重计算把排序、过滤、格式化移到 ViewModel 或预计算复用DateFormatter、NumberFormatter避免在body里创建对象。视图体复杂度控制当视图体包含超过约 10 个直接子节点时拆分为独立子视图每个子视图创建 diffing 检查点SwiftUI 可以在输入未变化时跳过计算。缩小状态辐射范围状态定义在需要它的最近的共同祖先用Observable的细粒度观察避免在根节点持有所有状态。环境使用的黄金法则环境应主要用于稳定的、长生命周期的对象频繁变化的值应该包装在Observable对象中避免向环境写入频繁变化的值因为可能导致大量“跳过更新”的累计成本。稳定列表 ID用数据本身的唯一标识符不要用UUID()确保Identifiable的id在数据生命周期内不变。懒加载容器超过约 20 个项目的列表用LazyVStack/LazyVGrid在懒加载容器中避免顶层条件语句把它们包装在VStack中以确保始终产生一个视图。图片优化下载合适尺寸的图片在后台线程解码使用图片缓存。避免 AnyView用ViewBuilder或Group代替AnyView在懒加载容器中尤其要避免AnyView。8.3 架构层面的优化ViewModel 职责单一一个 ViewModel 不要管太多东西。拆分成多个小 ViewModel。状态分层局部状态用State页面状态用 ViewModel全局状态用 Store。依赖注入用协议定义依赖方便测试和替换。异步加载用.task和async/await不要在onAppear里做同步阻塞操作。8.4 持续监控在 Debug 版本中嵌入性能面板显示body评估次数、内存占用、CPU 使用率。在 CI 中运行性能测试用XCTest的measure方法测量关键操作的耗时。定期用 Instruments 分析不要等到用户抱怨才去查。九、常见坑与调试建议9.1 在 Debug 模式下分析性能Debug 模式有额外的调试代码性能数据不准确。性能分析必须在 Release 模式下进行。9.2 忽略 View Body 评估次数body评估次数是 SwiftUI 性能的核心指标。如果一个 View 的body被评估了远超预期的次数说明状态辐射范围过大。9.3 用 UUID() 作为列表 ID每次渲染生成新 UUID 会导致列表重建。用数据本身的 ID。9.4 在 body 里创建 DateFormatterDateFormatter的创建非常昂贵。应该在 ViewModel 或工具类中复用。9.5 视图体超过 10 个节点超过约 10 个直接子节点后SwiftUI 无法高效地隔离变化。拆分为子视图。9.6 向环境写入频繁变化的值环境写入会导致该修饰符下所有读取任意环境键的子视图被无效化即使它们的视图体不重新执行也有显著的“跳过更新”成本。把频繁变化的值放在Observable对象中。9.7 在懒加载容器中使用顶层条件语句这会导致视图被保留在内存中即使滚出屏幕也无法释放。把条件包装在VStack中。9.8 忽略图片解码成本大图解码是 CPU 密集型操作。在后台线程解码使用合适尺寸的图片。9.9 忘记取消定时器和观察者在 View 消失时取消定时器、NotificationCenter 观察者、网络请求。9.10 闭包中捕获 self 导致循环引用用[weak self]打破循环引用。9.11 懒加载容器中的 ViewModel 泄漏使用独立的类持有列表项行视图用weak var引用 ViewModel。9.12 不做性能测试性能测试应该像单元测试一样成为开发流程的一部分。十、第8课总结这一课我们学习了 SwiftUI 的性能调优与调试。我们学到了SwiftUI 的渲染流程状态变化 → 重新计算 body → Diff 比较 → 更新渲染性能问题分两类长更新一次更新太慢和频繁更新许多小更新太频繁修复方式不同用 SwiftUI Instrument 找到瓶颈必须在 Release 模式下分析SwiftUI Instrument 的关键轨道Update Groups、Long View Body Updates、Long Platform View Updates、Other Long Updates以及 Cause and Effect Graph视图体应控制在约 10 个直接子节点以内超过后拆分为独立子视图创建 diffing 检查点长更新的修复把重计算移出 body、复用 DateFormatter、后台解码图片频繁更新的修复状态下移、拆分 ViewModel、用_printChanges()调试环境写入的隐藏代价写入任意环境键会无效化该修饰符下所有读取环境键的子视图即使视图体不重新执行也有“跳过更新”成本。环境应主要用于稳定的、长生命周期的对象列表优化稳定 ID、Lazy 容器、避免懒加载容器中的顶层条件语句内存泄漏检测Debug Memory Graph、Instruments 的 Leaks懒加载容器中的 ViewModel 需要用 weak 引用避免 AnyView它会破坏视图身份用ViewBuilder或Group代替性能优化的黄金法则先测量再优化优化后再次测量。性能优化让 App 不只是“能用”而是“好用”。学会它你的 App 就不只是“能跑”还能“跑得优雅”。如果把整个 App 比作一辆车布局是车身交互和动画是仪表盘和灯光持久化是油箱网络是引擎架构是底盘发布是上路性能优化就是发动机调校和悬挂调校——它决定了这辆车是“能开”还是“开得爽”。十一、练习练习一用 SwiftUI Instrument 分析列表滚动创建一个包含 100 行的列表用 SwiftUI Instrument 分析滚动时的body评估次数。找出评估次数最多的 View。练习二优化 body 中的重计算找一个在body里做排序或过滤的 View把计算移到 ViewModel 中。对比优化前后的性能数据。练习三修复不稳定 ID创建一个使用UUID()作为ForEachid 的列表观察滚动行为。改成稳定 id对比差异。练习四拆分复杂的视图体找一个视图体包含超过 10 个直接子节点的 View拆分为独立子视图。用_printChanges()观察优化前后哪些状态变化触发了更多或更少的更新。练习五环境写入的代价创建一个使用.environment()写入频繁变化值的 View用 SwiftUI Instrument 观察有多少视图被无效化。改用Observable对象包装对比优化前后。练习六检测内存泄漏写一个闭包捕获self的 ViewModel用 Debug Memory Graph 找到循环引用用[weak self]修复。练习七在 Release 模式下分析把你的 App 在 Release 模式下用 Time Profiler 分析找到 CPU 热点优化它。练习八Cause and Effect Graph 分析用 SwiftUI Instrument 的 Cause and Effect Graph 分析你的 App 的数据流找出不必要的视图更新。下一课预告第9课我们会学习自定义布局Layout协议深入高级动画PhaseAnimator、KeyframeAnimator、matchedGeometryEffect多平台适配iOS、iPadOS、macOS、watchOS、visionOSSwiftUI 与 UIKit 的混合开发。性能让 App 跑得优雅进阶技巧让 App 走得优雅。我们下一课见。记住一句话性能优化不是一次性的工作而是一种习惯。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑