关于 swift-architecture
swift-architecture 是面向 Swift 开发的架构选择与迁移技能,帮助开发者根据状态、依赖和副作用等实际需求选择合适模式。它默认建议简单的 SwiftUI 功能采用 MV,并提供逐步升级和测试指导。
主要功能
- 01按实际复杂度选择架构模式
- 02以 MV 作为 SwiftUI 默认起点
- 03明确状态所有者与依赖边界
- 04规划按功能切片的渐进迁移
- 05通过状态与依赖失败测试验证
适用场景
- 01为新 SwiftUI 功能选择架构
- 02判断何时从 MV 升级到 TCA
- 03逐步迁移既有功能边界
- 04检查依赖注入与状态转换设计
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swift-architecture安装后在 Claude Code 中输入/swift-architecture确认已加载。
附带文件evals/evals.jsonreferences/architecture-patterns.md
详细介绍
Swift 架构
选择能明确状态归属、依赖、副作用和测试的最小架构。新的 SwiftUI 功能默认采用 MV;只有在观察到复杂性时才升级。
目录
范围边界
本指南负责模式选择、模块边界、依赖方向、迁移策略以及架构层面的测试接缝。SwiftUI 属性包装器的连接方式和视图组合请参阅 swiftui-patterns,导航 API 和路由模型请参阅 swiftui-navigation,隔离诊断请参阅 swift-concurrency,测试语法和测试固件请参阅 swift-testing。
决策流程
- 记录功能的状态归属者、输入、输出、依赖、副作用、导航交接方式以及现有测试。
- 找出具体压力:复杂状态机、共享派生状态、依赖控制、功能组合、团队所有权或 UIKit 导航。
- 选择能应对该压力的最小模式;记录它增加了什么,以及哪些内容保持不变。
- 使用注入的依赖和可观察的状态转换,实现一个垂直切片。
- 运行现有行为测试,以及状态转换和依赖故障测试。如果行为发生变化,恢复测试固件,修复最小边界,然后重新运行测试,再迁移另一个切片。
模式选择
| 模式 | 适用场景 | 主要代价 |
|---|---|---|
| MV | SwiftUI 功能的状态和编排都比较简单 | 如果不拆分,逻辑可能会逐渐堆积在大型视图中 |
| MVVM | 表现逻辑需要一个可独立测试的适配器 | 额外层可能沦为单纯转发的壳 |
| MVI | 最适合将功能建模为显式的状态 + 意图 + reducer/effects | 样板代码,以及集中式转换设计 |
| TCA | 多个可组合功能都需要确定性 effects、依赖和测试 | 框架学习成本和架构层面的承诺 |
| Clean Architecture | 大型产品需要在 domain/data/UI 之间严格控制依赖方向 | 协议和映射的额外开销 |
| Coordinator | UIKit 或混合导航需要独立的流程归属者 | 多一个生命周期和路由归属者 |
| VIPER | 维护已有且采用成熟 VIPER 边界的 UIKit 模块 | 形式繁琐;不适合作为新 SwiftUI 功能的默认方案 |
如果压力来自导航复杂性,可将 Coordinator 与另一种状态模式搭配使用;它不能替代 domain/state 架构。
MV 默认方案
让视图表达状态,并将业务操作放入可观察模型和注入的服务中:
@MainActor
@Observable
final class TripStore {
private let client: TripClient
var trips: [Trip] = []
var error: Error?
init(client: TripClient) { self.client = client }
func load() async {
do { trips = try await client.fetchTrips() }
catch { self.error = error }
}
}
struct TripList: View {
@State private var store: TripStore
init(client: TripClient) {
_store = State(initialValue: TripStore(client: client))
}
var body: some View {
List(store.trips) { Text($0.name) }
.task { await store.load() }
}
}
加载 架构模式方案,查看 MVVM、MVI、TCA、Clean Architecture、Coordinator 和 VIPER 的结构。
升级信号
- 当大量表现转换必须在不渲染视图的情况下进行测试,且适配器确实包含行为时,选择 MVVM。
- 当转换、无效状态和 effects 需要一条可审计的 reducer 式路径时,选择 MVI。
- 当多个模块反复需要功能组合、依赖覆盖、取消以及确定性的 effect 测试时,选择 TCA。
- 当多个交付层/数据层之间需要独立的 domain 规则和依赖方向时,选择 Clean Architecture。
- 当 UIKit/混合导航需要明确路由归属、复杂流程组合,或需要在视图控制器之外进行条件导航时,添加 Coordinator。
- 对兼容的旧版模块或有意进行的迁移保留 VIPER;不要仅因习惯而用它启动新的 SwiftUI 功能。
不要仅仅因为视图很长就升级架构。应先提取子视图、服务和职责明确的可观察模型。
迁移
每次迁移一个功能边界:
- 通过测试和依赖项/状态清单固定现有行为。
- 在现有操作外围引入目标边界。
- 一次迁移一个状态转换或依赖项,不要同时重写 UI 和持久化逻辑。
- 每完成一部分,就比较行为、导航、取消、错误和持久化结果。
- 只有在没有调用方或测试依赖旧路径后,才移除它。
从 ObservableObject 迁移到 Observation 时,在替换包装器之前,先保持相同的所有者和变更隔离。从 MVVM 迁移到 MV 时,只有在视图已绑定到相同的模型/服务行为后,才删除仅用于转发的视图模型成员。采用 TCA 时,先封装一个功能的状态/操作/效果,再逐步迁移依赖项。
常见错误
| 错误 | 修正方法 |
|---|---|
| 因为某种模式流行而选择它 | 根据观察到的功能压力来选择。 |
| 视图模型只转发属性 | 移除它,改用 MV。 |
| 一个对象同时负责导航、网络、格式化、持久化和 UI 状态 | 按职责和依赖方向拆分。 |
| 在简单屏幕上套用 TCA 或 Clean Architecture | 从 MV 开始,并保留升级空间。 |
| 将 Coordinator 用作状态架构 | 让它专注于路由/生命周期的所有权。 |
| 在一个功能中混用多种模式 | 定义一种局部状态/效果模型,并在功能边界处迁移。 |
| 一次性大规模迁移 | 每次迁移一个经过测试的纵向切片,并重新运行同一套验证矩阵。 |
审查清单
- 选择依据具体的功能/团队压力给出说明
- 明确状态所有者、变更路径、依赖项、效果和导航所有者
- 注入依赖项,使其可在测试中替换
- 模式成本与功能复杂度相称
- UI 机制、导航 API、隔离和测试语法转到相关的技能文档
- 每次迁移一个纵向切片,并保留现有行为
- 每次迁移后,验证失败、取消、导航和持久化行为
- 不留仅用于转发的层或上帝对象