你好,旅行者!
AI agent 的 skill 是一组小型指令包,用来告诉代理在某一领域如何工作。它可以定义规则、工作流、项目约定、评审清单、参考资料和示例。实际使用中,skill 能把通用的 AI 辅助,变成更聚焦、更可复用的建议。
这篇文章是关于 agent skill 的系列文章之一。如果你想先看全局脉络,我在这里更完整地介绍了这个想法。
这一次,我想给出一个更实用的例子:一个 SwiftUI 性能 skill。
与其让模型去“review SwiftUI 性能”并希望它记住所有关键点,不如给它一套结构化 skill。这个 skill 说明了要关注什么、哪些模式危险、哪些 API 重要,以及如何在不盲目做微优化的情况下进行性能推理。
SKILL.md
这个 SwiftUI 性能 skill 只服务于一个窄场景:在更新行为很关键时审查 SwiftUI 代码。
它不是通用 SwiftUI 助手,不负责语法问题、样式问题、UIKit-only 性能优化,也不是给 Instruments 做通用教程。该 skill 应用于与 SwiftUI 更新有关的场景:不必要的失效重算、身份不稳定、状态读取过宽、body 内过重逻辑、行体积过大、滚动卡顿、布局成本、绘制成本、动画抖动,或异步生命周期在错误位置触发。
SKILL.md 的 description 非常关键。在 OpenAI Codex 里,skill 可以由用户显式选中,也可以由任务语义隐式匹配。也就是说,描述要足够具体以便在正确时触发,也要足够严格以避免误触。这个 skill 就做到了:它列出了应当启用的 SwiftUI 性能场景,也列出了不应启用的场景。
这个 skill 的核心思想是“变更局部化”。
在给出修复建议前,代理先要回答三个问题:
- 变更了什么?
- 哪些视图依赖了这次变更?
- 由此触发了多少 UI 工作?
这是一个很好的 SwiftUI 心智模型。很多 SwiftUI 性能问题并非一行明显“慢代码”导致,而是一个小状态改动却导致过多 UI 失效重算、视图读取了过宽的模型、身份频繁变动,或某一行在每次 SwiftUI 重新计算时都执行昂贵逻辑。
skill 进一步把这个模型落成了评审流程。它要求代理先检查身份,然后看 body 成本、依赖范围、状态生命周期、列表结构、闭包相关输入、布局与绘制成本、异步生命周期,并在有价值时再做 profiling。顺序很重要,因为这样能让评审尽量贴近真实代码路径。代理不该一上来就建议大规模架构重构,而在局部修正就能使更新路径更局部的情况下。
“证据规则”是另一个关键部分。它要求把静态评审结论、可疑风险、假设、真实测量结果、用户提供的证据、以及工具生成的证据分开。这样能避免性能讨论中的常见问题:先凭感觉“瞎造数字”。skill 可以说“父视图读取了整个模型并可能导致整屏失效重算”;但不能声称耗时 500ms,除非有真实测量支持。
在日常使用里,这个 skill 在给定具体 SwiftUI 代码路径时最有效,比如某个屏幕滚动变慢、一行更新太频繁、某个 view model 让整屏失效重算,或分页触发过于频繁。代理便可按 SKILL.md 的评审格式输出:指出核心问题、说明为何在 SwiftUI 下重要、评估风险、给出有针对性的重构建议、必要时补代码,以及若结果需要确认应测哪些指标。
skill 也给出了红线清单。部分显眼:.id(UUID())、可变集合里的基于索引身份、在 body 中排序。部分容易被忽略:隐藏了许多状态读取的宽泛计算属性、可以用 key-path binding 的地方却用了自定义 binding、行视图里堆积大量非视觉闭包、以及每一行重复使用复杂阴影与叠加层。
重构建议保持实用:保持身份稳定;将昂贵转换移出 body;在复用渲染路径前准备好 render model;把状态贴近真正拥有它的组件;按依赖边界拆分大视图,而不是按文件大小拆分;只有在视觉相等性明确且比重算更省时的场景才用 .equatable();当任务与变化输入绑定,应使用 .task(id:) 以便自动取消并重启。
最后一点也体现了整个 skill 的基调:没有“万能方案”。.equatable()、缓存、memoization、UIKit fallback、profiling 工具都能在合适场景派上用场,但代理要先把 SwiftUI 根因讲清楚。
SKILL.md 只是入口。它定义了范围、评审模型、证据规则和响应格式。更深入的内容在 references 里,分别覆盖 identity 与状态、Observation、body 成本、列表与分页、闭包与 binding、布局与绘制、异步生命周期、以及 profiling 验证。
异步生命周期与 MainActor
这份参考文档覆盖了 SwiftUI 生命周期和 Swift 并发交汇的地方。
核心观点很直接:SwiftUI 中的异步工作必须有明确 owner 和稳定触发器。任务应由明确的生命周期事件或用户动作启动,而不该因为 body 再次运行而被动触发。
这也是文档中强调 body 的原因。SwiftUI 可能频繁计算 body,如果在其中启动 Task { ... },就把渲染变成副作用来源。加载逻辑通常应放在 .task、.task(id:)、.refreshable、显式动作,或当工作需要跨越一个视图展示周期时放在 model-owned task 中。
文档还说明了 .task(id:) 的使用场景。它适合“当某个语义输入变化时应取消并重启”的工作,例如账号 ID、搜索关键词、当前 tab、过滤器、路由、文档 ID。ID 应该稳定且有语义;随机 ID、时间戳,或由任务自身变化的值都会带来意外重启。
另一大点是取消机制。Swift 的取消是协作式的,任务被取消后可能仍会运行,直到检查到取消信号。对于 SwiftUI,危险在于旧结果被提交到可见状态。搜索、分页、快速切 tab、详情切换都可能出现过期结果回写。任务在更新 UI 之前应检查取消,还应确认当前输入仍与该任务一致。
MainActor 部分尤其重要。@MainActor 的 view model 通常是 UI 状态的合适位置,但不该变成承接大量同步重活的篮子。排序、过滤、解析、格式化、构造 render model 这类工作若在主线程热点路径执行,会阻塞交互。最终提交到 UI 的状态可在 MainActor,重计算/转换工作可迁移到主线程之外(当数据可安全转移时)。
文档也点名行生命周期和分页。onAppear 并不可靠,无法代表“首次可见”。行在滚动、过滤、刷新、导航或 identity 改变时可能重复出现。分页逻辑需要 guard:是否正在加载、是否有下一页、请求键是否重复、刷新是否重叠、是否可取消、是否出现过期触发。对长列表,footer/sentinel 常常比给每一行绑定生命周期更可靠。
实际中,这份参考教会代理直接问:异步工作是谁发起的?是否会意外重启?是否会重复执行?旧任务会不会覆盖新结果?是否有太多工作在 MainActor 上执行?
更偏向实践的修复通常是:让触发更稳定;保证操作幂等;取消逻辑与真实错误分离;防止过期提交;把 UI 状态更新聚合提交,而不是逐条 item 更新。
这展示了该 skill 如何避免“SwiftUI 慢”这类泛化判断。问题应更具体地被命名为:重复任务、身份重启不稳定、过期结果提交、主线程转换开销、以及异步更新导致过多 UI 失效。
Body 成本与 render model
这份参考覆盖了一个非常常见的 SwiftUI 性能问题:在视图渲染时做了过多工作。
body 应该负责描述 UI,而不应充当数据处理流水线。参考文档聚焦那些会不小心被放进渲染路径的工作:排序、过滤、分组、映射、格式化、解析、图片处理、昂贵计算属性、以及反复构造 render model。
重要细节是:它并不禁止所有 body 内转换。一个列表项里的一次 .formatted(...) 在静态场景通常没问题;问题在于相同工作在高频场景频繁执行、涉及大量 item、发生在滚动/输入期间、产生大量临时对象,或主线程互动路径上持续运行。
文档也区分了两个问题。依赖范围决定“重算频率”;body 成本决定“每次重算有多贵”。拆分视图只有在把昂贵工作移到更新路径外、或用更窄的依赖边界包住时才有用。把重流程从 body 挪到 computed property 并不能解决问题,如果 body 仍每次读取它。
推荐模式是在输入变化时预先准备 render-ready model。行模型可以包含稳定身份、展示字符串、标记、标签、轻量样式状态。这样 SwiftUI 渲染的是简化值,而不是每次都重建格式化文本、排序数组、分组结果或展示模型。
这个做法对 feed、仪表盘、财务行、目录网格、timeline、搜索结果这类重复内容尤其有效——原始数据并不直接适合展示时。它不该机械应用于每个小视图,只有当它减少重复工作、或让失效路径更可控时才值得。
MainActor 仍然关键。UI 相关状态一般应在 MainActor,但重同步处理不应长期在主线程热点路径执行。对于较大纯计算,可以采用:先接收原始数据 -> 在主线程之外构建展示模型 -> 最后一次性提交紧凑状态更新。文档提醒:加了 async 并不自动把 CPU 工作移出主线程。
验证上,文档给出 Time Profiler、SwiftUI Instrument、Allocations、signpost、时间戳、XCTest 性能测试等。代理应在问题可疑但不明显时使用,不应没测量就宣布数值收益。
实践里,这个参考训练代理判断:body 主要是在装配展示值,还是在每次更新时都在重建展示所需的数据?如果是后者,通常应改为“输入变更时做一次转换、准备稳定 render model、再保持 UI 更新量小”。
闭包、binding 与 equatable views
这个参考关注让 SwiftUI 的输入更易于推断。
核心思想是把视觉数据和行为逻辑分开,尤其在大列表、复杂行、表单、菜单、滑动动作和高频更新父视图里。一行视图可读取视觉输入(文本、标志位、ID、颜色、render 数据),也可接收行为输入(闭包、手势、Action handler、binding、swipe action)。但行为型输入更难比较、更容易过度捕获,也更容易成为隐式依赖。
文档并不夸大问题。闭包本身没问题,闭包不必然导致行重绘。问题在于大列表中保留了大量非视觉闭包、捕获了大模型,或把纯展示与复杂动作路由混在一个行里。
实用修复是把 row 里可视部分聚焦,行为动作在更稳定边界附加。比如纯展示行只接收渲染模型,而 tap/swipe 这类动作只捕获稳定引用和 row ID。这样不必完全移除闭包,但会让行值更清晰,若后续使用 .equatable() 也更容易。
同样,自定义 binding 也不坏,但 Binding(get:set:) 容易把工作藏到渲染路径。若 key-path binding 能表达同样关系,通常更清晰。自定义 binding 适用于真实转换、校验、optional 处理、取整、或路由到 model 方法等场景。若使用 Observation,@Bindable 和 key-path binding 能让可编辑关系更明确。
.equatable() 的建议同样刻意收敛。只在视觉输入清晰、等值检查成本低于 body 重算时使用。若视图读取隐藏状态、依赖环境值、携带变化闭包,或在 equality 中遗漏可见状态,equatable() 可能反而让更新行为更难信任。
面对复杂行时,文档偏向 Equatable render model。让 equality 只覆盖真正影响显示的字段:标题、副标题、格式化金额、状态标记、可见性、布局相关值等。
实践中,这个参考避免了“去掉闭包”“加 .equatable()”这类泛化建议。更好的问题应当是:视觉输入、动作路由、binding 逻辑、是否有清晰的可复现 equality 边界,这些是否足够让 SwiftUI 的更新行为可理解、可验证。
Identity 与状态
这份参考聚焦于 SwiftUI 最关键问题之一:SwiftUI 是否在更新时保持了正确身份?
SwiftUI 视图是临时值描述,状态不存于 view 值本体内。SwiftUI 通过身份绑定层级状态,结合结构位置、具体视图类型,以及 ForEach/.id(...) 等显式身份。
这很重要,因为身份误用会同时带来正确性和性能问题。文本输入可能丢失焦点,输入状态会“跳位”,展开状态可能回到错误行,.task 被重复执行,动画被重置。SwiftUI 可能重建比预期更多的树。
文档从结构身份开始。两个外观类似的分支可能仍是不同结构;当 UI 真的在不同状态时没问题;但当同一概念性组件应当保留局部状态只改值时,就不该在运行时切换结构,值语义的修改器往往更安全。
同理,文档也提醒自定义条件性 modifier helper。它们方便,但容易把结构变化藏在链式写法后。若该条件在组件生命周期内变化,子树可能重新拿到不同身份,导致 @State、@FocusState、@StateObject、生命周期工作、手势或动画状态重置。文档并未禁用这类写法,而是要求代理判断条件是否静态、是否有意、是否会伤害局部状态。
显式 .id(...) 被视为身份边界,不是刷新按钮。它适合子树需要按实体重置时使用,或定义滚动锚点、已知生命周期边界。若 ID 不稳定,如 .id(UUID()),会导致每次更新都换身份。
对于集合,规则是稳定数据身份。可变/可过滤/可重排集合不应使用索引或偏移作为 ForEach 的 id,否则插入、删除、过滤、重排后行状态会归到错误 item。ID 应来自稳定数据、唯一且开销低。
状态部分同样实用。@State 用于稳定视图身份下的局部值状态,不能当作父级输入的“快照缓存”。若 @State 通过输入初始化,它只是一份初始快照:用于本地草稿没问题,但若应始终同步父级变化则不对。
文档还澄清 model 所有权:view 自己创建并持有的 ObservableObject 用 @StateObject;从外部注入的 model 用 @ObservedObject。这是所有权和生命周期规则,不是“哪个更快”。在 iOS 17+,自有 @Observable 模型可放在 @State 中,子视图需要可编辑绑定时用 @Bindable。
实践上,这个参考让代理先修复身份问题,再讨论更底层优化。身份不稳定时,SwiftUI 可能重置状态、重启工作,甚至把状态挂到错误行。修复通常不复杂:使用稳定 ID、让 .id(...) 用得更有意图、把局部状态放在稳定子树、用匹配所有权的 state 包装器。
布局、绘制与动画
这份参考覆盖 SwiftUI 的视觉性能:布局、绘制、合成与动画。
目标并非去掉阴影、遮罩、模糊、渐变、过渡和动画这些效果,而是让其成本与使用范围成比例。一张卡片上的 blur 和长列表中每一行都叠加 blur/mask/shadow/overlay + 动画,是完全不同的量级。
文档先做问题分类:抖动是否来自布局、绘制、合成、动画范围,还是来自可见更新时的主线程其他 CPU 开销。这个区分很关键,因为“SwiftUI diffing 慢”并不总是根因。
长长的 modifier 链不一定错误,但在重复渲染或高频更新时值得审查。background、overlay、mask、clipShape、shadow、blur、drawingGroup()、compositingGroup() 等效果在热点路径应谨慎。
GeometryReader 也一样。它确实有用,比如需要容器尺寸或坐标;风险在于每行都读它、把读值写入宽泛可观察状态、或形成测量-状态-布局反馈循环。若可能,把测量放在稳定容器边界,或用更简单布局 API。
Preference key 有用,但也可能增加更新循环。建议让 preference 值尽量小,在容器级别复用时比每行使用更好,对写回状态的浮点值做好抖动阈值过滤,避免每次微小变化都触发大量重算。
视觉效果部分讨论了合成压力。重复动态阴影、动画模糊、嵌套 mask、半透明材料、大图裁剪、复杂 overlay 在滚动/动画中可能非常贵。该 skill 不说“禁止用”,而是建议在设计允许时减少热点路径、将昂贵效果上提到更粗粒度边界,并在怀疑时验证。
drawingGroup() 和 compositingGroup() 被当作有选择的工具,不是万能开关。它们在某些复杂矢量场景有帮助,但也可能转移成本到离屏渲染、内存与纹理。不能机械建议每行都加。
对于高密度绘制,文档提到 Canvas。它适合图表、波形、热图、sparkline、密集粒子等“多元素同步更新且无需每个元素独立 SwiftUI 视图”的场景;但这类内容在可访问性、命中测试、状态生命周期上不一定与普通控件等价。
动画指导主要在于范围控制。对大容器做隐式动画会让大量无关状态参与一个事务,导致动画范围失控。更推荐把动画尽量贴近具体视觉变化组件。一般来说透明度和 transform 通常比布局/文本/blur/mask/shadow/gradient 动画更便宜,但即使它们也会在重路径下抖动。
实践上,这份参考教会代理更精确地描述视觉性能问题:不是“少点 modifier”,而是指出具体风险点——每行几何计算、feedback 到广域状态的 preference 写入、可被 Canvas 合并的密集子图、动画范围过宽、列表滚动中重复合成效果。
列表、分页与行
这份参考聚焦于最容易把 SwiftUI 搞慢的场景之一:大规模、增长型或高频变更的集合。
主旨仍是更新局部性。好的列表应让变化边界清晰:一行、一个分页、一条 footer、一项选中、或一个过滤结果。若每次 append/filter/sort/小变更都让 SwiftUI 重建大量不稳定结构,滚动和分页就会变脆。
文档先从症状入手,而不是直接下“应改为哪种容器”。滚动抖动、重复分页请求、append 慢、内存增长、无关行更新、搜索延迟都可能是不同原因,代理需先把症状对应到更具体的热点路径后再建议重构。
它也避免“默认把 List 换成 LazyVStack”这类误用。List、LazyVStack、VStack 不是同质替代:List 在需要原生长列表行为、selection、editing、swipe actions、无障碍体验时通常更合适;LazyVStack 提供更强布局控制用于自定义卡片流,但“lazy”不等于 UIKit 式 cell reuse。VStack 更适合小固定内容,不是长动态列表。
identity 是另一个重点。大集合要有稳定且廉价的数据身份。对于可插入、删除、过滤、重排、分页的集合,用偏移量做 id 很危险;var id: UUID { UUID() } 这类写法更糟,因为每次访问都会变身份。
行应该便宜。格式化、子数据排序、图片处理、图标查找、数据库读取、长 modifier 链、overlay/mask/shadow/blur、自定义 binding、复杂菜单都可能在大量行里放大成本。文档不禁用这些,而是问:是否发生在热点路径?是否能改为渲染模型。
分页也单独强调。如果行模型简单、identity 稳定,直接 append 往往没问题;若出现 append 抖动、旧行重建、或分页时触发行昂贵逻辑,就要警惕。此时稳定分页/分组模型可让变更单位更清晰。重点不在把 Section 当魔法,而在于保留真实分页边界让 SwiftUI 与开发者更容易推理。
文档还警惕在 body 内派生分页。渲染时将数组拆分页会分配新页模型、制造不稳定身份、隐藏真实分页边界。分页、可见行、过滤、排序、行渲染模型应在输入变化时就准备好,而不是在渲染列表时重算。
分页触发是另一个常见 bug 点。行 .onAppear 不是可靠的一次性事件,在滚动、导航、过滤、刷新或重建视图时可重复触发。更稳妥是使用带 guard 的 .task(id:)、footer 或 sentinel view。模型方法应幂等,并在提交可见状态前检查 loading、是否有下一页、已请求页签、过滤/搜索上下文、以及取消状态。
参考资料也对把重工作放到后端线程持现实态度。异步处理行准备可有帮助,但它不会让工作“免费化”;如果后台处理本身抢占 CPU,滚动流畅度仍会受影响。最终更新应精简,只提交紧凑 UI 变更,重 CPU 工作只有在数据安全可移到后台且收益足够大时再移出主线程。
实践上,这份参考教代理把列表当作更新路径审核,而非容器本身。关键问题是:输入变化后,列表结构要重建多少?行是否昂贵?身份是否稳定?分页是否只更新清晰的单位,而非扰乱整屏?
Observation 与依赖
这份参考覆盖依赖范围:一个视图读取了哪些状态,以及某个状态变化会影响多少 UI。
目标是局部失效:状态变化应影响最小足够的 UI 区域。如果只有一个 header 需要 model.title,不应让整屏都对 model.title 产生依赖,除非父视图确实都需要。
文档对措辞很谨慎。SwiftUI 更新受依赖、identity、environment、transaction、animation、父级更新共同驱动,所以不能说“这个视图只在某属性变化时重绘”。更准确是:“该视图读取了 model.name,因此对 model.name 的变化来说,model.name 会通过这条依赖触发该视图重评估”。
一个重点区分是 ObservableObject 与 Observation。ObservableObject + @Published 下,子视图常常以对象粒度观察,即使仅需 name,也可能对完整对象敏感。iOS 17+ 的 Observation 可更精确追踪属性读,因此子视图仅读 model.name 时,不一定依赖该模型所有字段。
但 Observation 并非万能开关。若父视图在传参前已读取大量属性,父视图就会持有这些依赖。文档提出“依赖域”概念:按特定可观察状态建立小子树。某些时候传值更好,某些时候传模型更好,取决于依赖应属于哪里。
environment value 也同理。小展示视图若读取了大范围 app model,会把真实更新路径隐藏起来。复用视图通常用显式参数或更小模型更易于审核。
computed property 也是隐藏依赖来源之一。视图看似只读 headerTitle,但该计算属性内部可能读了 user、accounts、marketState 等多项,导致依赖范围放大。若要分析依赖,应暴露这些隐藏读取。若是性能计算本身,应转到 body cost 参考。
集合方面的建议也很实用。为了展示未读数而让一个 badge 读取整个 messages 数组,会使依赖扩大并可能反复计算。预计算 unreadCount 能缩小依赖范围,但前提是模型能保持其与源数据一致。
迁移建议是:在 iOS 17+ 若项目不受 Combine 依赖且无旧系统要求、UIKit 消费者较少,新的 SwiftUI-facing 模型可优先考虑 @Observable。View-owned 的 @Observable 模型可放 @State,只有确需绑定时才用 @Bindable。已有的 ObservableObject 代码不应机械替换,尤其当有 Combine 生态、UIKit 依赖或老系统兼容时。
实践中,这份参考让代理在重构前先问:状态读取发生在哪里?若一个大父视图读取本该由子视图使用的数据,先把读取下沉;如果子视图应是纯展示,就传窄值;如果依赖应归属小子树,创建依赖域。最好的方案是让依赖关系一眼可见。
Profiling 与验证
这份参考专注于证据。
当任务涉及 Instruments traces、xctrace 输出、signpost logs、XCTest benchmark、MetricKit payload、录屏、内存图、控制台日志或其他性能产物时,它负责的是“确认或反驳”一个具体的性能假设,而不是替代代码评审。
核心规则很严格:没有真实证据不要声称已测量。可疑行体是静态风险;_printChanges() 的重复日志是调试信号;Time Profiler trace、signpost interval、XCTest metric、memory graph、MetricKit payload 才是证据。不同层级不能混淆。
该规则很关键,因为性能讨论常从“看起来有风险”直接跳到“它导致了 200ms 卡顿”。没有 trace、benchmark、日志或用户测量,代理应只给出风险/假设并说明如何验证。
参考文档也要求在 profiling 前定义场景。仅说“app 感觉慢”不够,必须给出屏幕、起始状态、具体动作、数据规模、设备/模拟器、系统版本、构建配置和复现症状。例如:打开一个有 2000 行的 feed,输入 5 个字符并观察每次按键是否重算所有行。
工具选择要按问题决定。SwiftUI Instrument 用于 body 更新和更新范围;Time Profiler 用于主线程 CPU、formatter/sorter/mapper/parser、图片处理、昂贵符号;Animation Hitches / Core Animation 用于可见卡顿、滚动 jank、合成压力、mask/shadow/blur 与大面积动画;Allocations 用于临时数组/字符串、formatter 创建、render model 重建、包装对象抖动;Memory Graph 用于保留问题、泄漏的 model、未取消任务、闭包、publisher/stream/delegate/cache。
文档将 signpost 看作阶段标记,而非万能指标。对状态 mutation 加 mark 能对齐事件与 timeline,但不自动给出完整 SwiftUI diff/layout/draw/render 成本。
MetricKit 被视为生产期证据,但不是本地 profiler。它适合看趋势(卡顿、响应、内存、CPU、能耗、启动时间、诊断)在版本和设备类别上的变化,通常用于定位方向,而非直接告诉哪一行改。
before-after 循环很简单:定义一个场景、拿 baseline、形成一个假设、应用一个针对性重构、重复同场景、比对同一指标。文档反对一次改多个点,因为那样结果难以解释。
实践里,这份参考教代理让性能主张可证伪。若 Trace 显示 body 更新耗时长,检查依赖、identity、body 成本;若 Time Profiler 显示滚动时主线程格式化过重,把格式化移出 render path;若分页 append 出现尖峰,检查分页边界、行成本、主线程提交方式;若录屏有抖动但没 trace,就把它转成可复现的 profiling 场景,而不是猜测。
总结
一个 SwiftUI 性能 skill 最好不要变成把所有规则塞进一个巨大 prompt。
主 SKILL.md 应定义思维模型、范围、评审顺序、证据规则和响应格式,references 再分开处理深层主题:identity、dependencies、body cost、lists、closures、layout、async lifecycle、profiling。
这样的结构能让 agent 在真实 review 中更有用。它不再只说“拆分 view”或“用 Instruments”,而是能问出更好的问题:
我改了什么?
哪些视图依赖了这次改动?
identity 稳定吗?
状态是否挂在正确生命周期?
body 是否做了过多工作?
行是否足够轻?
异步工作是否绑定了稳定生命周期?
这个结论是有测量支持,还是仅仅假设?
这正是性能 skill 的价值。它不会让 agent 变“神”,但能让流程更窄,减少泛泛解释和无依据数字。
对 SwiftUI 来说,这尤其重要。性能问题往往隐藏在更新行为里:过宽状态读取、身份不稳定、重复格式化、行生命周期工作,或动画影响了超出预期的 UI。一个好的 skill 可以把评审压回这些真实原因。
完整的 SwiftUI 性能 skill 可见 GitHub 仓库。