常见代码异味与修复模式
意图
在代码优先审查期间使用本参考,将可见的 SwiftUI 模式映射到可能的运行时成本和安全修复指导。
高优先级异味
body中的昂贵 formatter
varbody:someView{letnumber=NumberFormatter()letmeasure=MeasurementFormatter()Text(measure.string(from:.init(value:meters,unit:.meters)))}优先在模型或专用辅助函数中缓存 formatter:
finalclassDistanceFormatter{staticletshared=DistanceFormatter()letnumber=NumberFormatter()letmeasure=MeasurementFormatter()}重量级计算属性
varfiltered:[Item]{items.filter{$0.isEnabled}}优先在模型/辅助函数中为每个有意义的输入变化派生一次,或仅在视图真正拥有转换生命周期时才存储派生的视图自有状态。
在body中排序或过滤
List{ForEach(items.sorted(by:sortRule)){iteminRow(item)}}优先在渲染工作开始之前排序:
letsortedItems=items.sorted(by:sortRule)ForEach内联过滤
ForEach(items.filter{$0.isEnabled}){iteminRow(item)}优先使用带稳定身份的预过滤集合。
不稳定的身份
ForEach(items,id:\.self){iteminRow(item)}对非稳定值或会重新排序的集合避免使用id: \.self。使用稳定的领域标识符。
顶级条件视图切换
varcontent:someView{ifisEditing{editingView}else{readOnlyView}}优先使用一个稳定的基础视图,并将条件局部化到分区或修饰符。这减少根身份变化并使 diff 更廉价。
在主线程上解码图像
Image(uiImage:UIImage(data:data)!)优先在主线程之外解码和降采样,然后存储处理后的图像。
观察扇出
iOS 17+ 上的宽泛@Observable读取
@ObservablefinalclassModel{varitems:[Item]=[]}varbody:someView{Row(isFavorite:model.items.contains(item))}如果许多视图读取同一个宽泛集合或根模型,小的变化可能扇出为广泛的失效。优先使用更窄的派生输入、更小的可观察表面,或更靠近叶子视图的逐项状态。
iOS 16 及更早版本上的宽泛ObservableObject读取
finalclassModel:ObservableObject{@Publishedvaritems:[Item]=[]}同样的警告适用于传统观察。当许多后代只需要一个派生字段时,避免让它们观察大型共享对象。
修复说明
@State不是通用缓存
将@State用于视图自有状态和有意属于视图生命周期的派生值。不要将任意的昂贵计算移入@State,除非你还定义了它何时以及为什么更新。
更好的替代方案:
- 在模型或存储中预计算
- 响应特定输入变化更新派生状态
- 在专用辅助函数中记忆化
- 在渲染前于后台任务中预处理
equatable()是条件性指导
仅在以下情况使用equatable():
- 相等性比较比重算子树更便宜,并且
- 视图输入具有值语义且足够稳定,可以进行有意义的相等性检查
不要将equatable()作为所有重绘的一刀切修复。
分诊顺序
当多个异味同时出现时,按此顺序优先处理:
- 广泛的失效和观察扇出
- 不稳定的身份和列表抖动
- 渲染期间的主线程工作
- 图像解码或调整大小成本
- 布局和动画复杂度