mcpField
S
swift-mvvm

swift-mvvm

github.com/tobitech开发856 次安装44 周前更新
代码仓库

关于 swift-mvvm

面向 Swift 编码 Agent 的 MVVM 技能,指导创建和重构 ViewModel。强调隔离 UI 框架依赖、拆分可测试逻辑,并通过协议注入依赖。

主要功能

  1. 01保持 ViewModel 不依赖 UI 框架
  2. 02将状态、意图与依赖协调分开
  3. 03拆分纯逻辑和副作用单元
  4. 04通过协议注入依赖
  5. 05管理主线程状态与任务取消
  6. 06提供 ViewModel 测试指导

适用场景

  1. 01为 SwiftUI、UIKit 或 AppKit 添加功能
  2. 02重构臃肿的 ViewModel
  3. 03排查状态管理与线程问题
  4. 04提升依赖模拟和状态测试能力

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tobitech/swift-mvvm --skill swift-mvvm

安装后在 Claude Code 中输入/swift-mvvm确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
references/
adding-new-features.md
file-naming-conventions.md
module-structure.md
common-patterns.md
mvvm-overview.md
integration-patterns.md
mvvm-style-guide.md
controller-vs-coordinator.md
anti-patterns.md
services-vs-feature-services.md
where-things-go.md
state-management.md
testing-considerations.md
templates/
AppKitRevealer.swift
AppKitViewController.swift
ObservationViewModel.swift
ServiceUseCaseControllerAndPureLogic.swift
CombineViewModel.swift
ProtocolAdapters.swift
ViewModelTests.swift
SwiftUICombineView.swift
SwiftUIObservationView.swift

详细介绍

Swift MVVM 技能

帮助 Agent 为 Swift 项目编写更好的 MVVM 代码。

核心原则

  • 让 ViewModel 不依赖 UI 框架。 ViewModel 不应导入 SwiftUI、UIKit 或 AppKit。
  • ViewModel 应主要由以下内容组成:(1)状态、(2)意图方法和(3)依赖协调。
  • 将工作拆分到更小、可测试的单元中(纯结构体/函数、用例、控制器、存储库、映射器、格式化器)。
  • 使用基于协议的依赖注入,确保所有依赖都可模拟。
  • 鼓励使用扩展来组织代码(尤其是协议遵循)。

适用场景

当用户要求执行以下操作时,使用此技能:

  • 在 SwiftUI/UIKit/AppKit 中添加或重构功能,同时保持架构清晰。
  • 创建或改进 ViewModel(状态、意图、效果),并将逻辑移出 View/ViewController。
  • 修复状态管理问题(包装器错误、线程警告、不稳定的绑定)。
  • 提高可测试性(基于协议的依赖、模拟对象/假对象、确定性的状态更新)。
  • 通过拆分职责来精简“臃肿的 ViewModel”。

首先检查(编写代码之前)

  1. 确定 UI 技术:SwiftUI、UIKit 或 AppKit。
  2. 确定部署目标:
    • 如果支持 iOS 17+/macOS 14+,优先使用 Observation(@Observable)。
    • 否则优先使用基于 Combine 的 ObservableObject 和 @Published。
  3. 遵循项目现有风格(命名、文件夹、依赖注入方式、网络层)。

MVVM 职责

View 层

SwiftUI View/UIKit UIViewController/AppKit NSViewController:

  • 声明布局并绑定到状态。
  • 将用户意图(点击、选择、文本更改)发送给 ViewModel。
  • 负责仅与 UI 有关的事项(导航、显示警告、AppKit 面板、第一响应者等)。
ViewModel
  • 管理屏幕状态(加载/数据/错误、派生的 UI 值)。
  • 通过注入的依赖协调效果。
  • 提供意图方法(onAppear()、refresh()、didTap…),而不是让视图随意调用内部方法。
Domain/Services
  • 封装数据获取、缓存、持久化、解码和验证。
  • 必须独立于 UI 框架。

硬性规则:ViewModel 不得导入 UI 框架

ViewModel 不应导入:

  • SwiftUI
  • UIKit
  • AppKit

如果 ViewModel 需要平台行为,应在非 UI 模块(仅依赖 Foundation)中定义一个小型协议,并在 View 层(或平台适配器模块)提供平台实现。

示例:

// In a Foundation-only target / file.
protocol FileRevealing {
  func reveal(_ url: URL)
}

// In AppKit layer.
import AppKit

struct WorkspaceFileRevealer: FileRevealing {
  func reveal(_ url: URL) {
    NSWorkspace.shared.activateFileViewerSelecting([url])
  }
}

Any “AppKit-y” or “UIKit-y” behavior inside the ViewModel is a **smell**.

## Preferred ViewModel shapes

Pick one based on scope.

### Pattern A: Simple screen

- `State` struct (nested)
- intent methods
- async `load()` with cancellation

### Pattern B: Complex screen

- `State` struct (nested)
- `Action` enum + `send(_:)`
- reducer-like switch for state transitions
- side effects delegated to injected units

## Keep state structured (avoid a ViewModel with 30 vars)

Prefer:

```swift
struct State: Equatable {
  var view = ViewState()
  var content = ContentState()
  var alerts = AlertsState()

  struct ViewState: Equatable {
    var isLoading = false
    var title = ""
  }

  struct ContentState: Equatable {
    var rows: [Row] = []
    var emptyMessage: String? = nil
  }

  struct AlertsState: Equatable {
    var error: ErrorState? = nil
  }
}

重构臃肿的 ViewModel(优先顺序)

精简大型 VM 时,按以下顺序重构:

  1. 首先提取纯逻辑
  • 将非 IO 计算移入纯结构体或纯函数。
  • 示例:筛选、排序、将领域模型映射为行模型、格式化、验证、状态归约器。
  1. 将副作用提取到控制器中(仍可测试)
  • 创建用于 IO 和编排的小型“效果”单元。
  • 通过协议封装,并注入 VM。
  • 示例:LoadExamplesUseCase、ExamplesController、AnalyticsTracking、FileRevealing。
  1. 让 VM 只保留状态和意图
  • VM 将意图转发给纯逻辑/效果单元,并设置状态。

并发与取消规则

  • UI 状态更改应在主 actor上进行。
  • 保存正在运行的任务,并在新请求开始或视图消失时取消这些任务。

SwiftUI 集成规则

Observation(@Observable)
  • 使用 @State 持有 ViewModel 实例(所有者),并将其向下传递。
Combine(ObservableObject)
  • 当视图创建/拥有 ViewModel 时,使用 @StateObject。
  • 当视图接收 ViewModel 时,使用 @ObservedObject。

依赖注入规则

  • ViewModel 在初始化器中接收依赖。
  • 依赖应为协议;为生产环境提供实现,并为测试提供模拟/伪实现。
  • 优先注入小单元:
    • 纯逻辑:RowBuilder、Validator、Reducer
    • 副作用:UseCase、Controller、Repository
    • 平台适配器:FileRevealing、URLOpening 等

避免臃肿的 ViewModel

问题表现:

  • 导入 UI 框架。
  • 直接处理 URLSession/JSON 解码。
  • 创建 NSAlert/UIAlertController。
  • 了解 NSWorkspace/UIApplication。
  • 在内联代码中使用复杂逻辑处理所有格式化。
  • 将导航、分析、网络和缓存都放在一起处理。

重构方式:

  • 提取纯逻辑 RowBuilder 或 Reducer。
  • 为业务规则提取 UseCase。
  • 为 IO 提取 Repository。
  • 提取副作用 Controller 来协调多个服务。
  • 为 UI 操作提取平台适配器(小型协议)。

输出要求

编写或重构时:

  • 按文件分组,以便于审查差异的代码块提供代码。
  • 优先使用小型、可组合的函数。
  • 使用扩展进行组织(例如,协议遵循、分组辅助方法)。
  • 使用清晰的命名:FooViewModel、FooState、FooUseCase、FooRepository、FooController、FooRowBuilder。
  • 为 ViewModel 状态转换和提取出的纯逻辑添加测试。

测试指南

新测试优先使用 Swift Testing(import Testing、@Test、#expect、#require)。

  • 直接测试纯逻辑单元(快速、确定)。
  • 通过注入模拟对象,测试 ViewModel 状态转换(成功/失败/取消)。

模板

复制/粘贴自:

  • templates/ObservationViewModel.swift
  • templates/CombineViewModel.swift
  • templates/AppKitViewController.swift
  • templates/ProtocolAdapters.swift
  • templates/ServiceUseCaseControllerAndPureLogic.swift
  • templates/ViewModelTests.swift(Swift Testing)

附加资源(仅在需要时加载)

评论

支持 Markdown