ワンクリックで
xxf-aaa-coding-arch
xxf_ios 项目的功能模块架构规范——按 Feature 分层组织代码(Api / Domain / Repository / Service / Di / Presentation)。新增或重构功能模块时必须遵守。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
xxf_ios 项目的功能模块架构规范——按 Feature 分层组织代码(Api / Domain / Repository / Service / Di / Presentation)。新增或重构功能模块时必须遵守。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
创建 XXF iOS 列表页模板(DiffableDataSource + BaseCollectionViewCell + StatefulView + 分页 ViewModel)
规范 ViewController 与 ViewModel 的分区组织方式。用于治理成员变量和方法过多、顺序混乱、阅读成本高的问题;通过 MARK 分区和职责分层保持代码导航清晰。
处理 XXF iOS 项目中的通用编码任务交付流程。用于 bugfix、功能开发、重构、回归修复等未显式指明测试或 review 的日常 coding 请求;负责自动串起模块 skill、补测、验证、代码审查与风险门禁。
iOS 性能门禁与主动鉴别。针对常规 coding 改动自动识别性能风险(主线程阻塞、列表卡顿、内存抖动、启动耗时、无效并发、过度渲染),并执行最小可行验证与门禁结论,不依赖用户额外提示。
Vibe Coding 通用治理闭环。用于把模糊需求转成可验证交付:先定义问题与边界,再走拼好码优先、最小改动、硬门禁验证、证据化交付和风险结论。适用于需求澄清、实现前规划、AI 协作治理、质量门禁落地。
XXFViewModel MVVM 的 VM 基类与生命周期。当用户要写 ViewModel、绑定 View、处理输入输出流,或询问"XXF 的 MVVM 怎么用"时使用。若出现 ViewModel 成员或方法膨胀、顺序混乱,应联动 `xxf-aaa-class-declaration-guidelines` 做分区治理。
| name | xxf-aaa-coding-arch |
| description | xxf_ios 项目的功能模块架构规范——按 Feature 分层组织代码(Api / Domain / Repository / Service / Di / Presentation)。新增或重构功能模块时必须遵守。 |
适用范围:在本项目新增 Feature 模块、重构已有模块、或跨层改动时,必须遵守以下目录与分层约定。
AppName/Sources/
├── Arch/ # 全局基础设施
└── {Feature}/ # 功能模块
├── Api/ # Moya TargetType 接口定义
├── Domain/DTO/ # 数据传输对象
├── Repository/ # 数据层(网络 + 缓存)
├── Service/ # 业务服务层
├── Di/ # 依赖注入注册
└── Presentation/
├── ViewModel/ # 业务逻辑
└── Page/ # ViewController
| 层 | 目录 | 职责 | 依赖方向 |
|---|---|---|---|
| Arch | Arch/ | 全局基础设施:基类、通用配置、跨模块工具 | 被所有 Feature 依赖 |
| Api | {Feature}/Api/ | 定义 Moya TargetType,只声明接口,不关心调用方 | 依赖 Domain/DTO |
| Domain/DTO | {Feature}/Domain/DTO/ | 纯数据模型(Codable),无业务逻辑 | 无依赖 |
| Repository | {Feature}/Repository/ | 网络请求 + 本地缓存的组合,对上层屏蔽数据来源 | 依赖 Api、DTO、缓存层 |
| Service | {Feature}/Service/ | 业务规则与跨 Repository 编排的唯一落点(见 §3.6) | 依赖 Repository |
| Di | {Feature}/Di/ | 模块内依赖注入注册(基于 XXFDi) | 依赖本模块其他层 |
| Presentation/ViewModel | {Feature}/Presentation/ViewModel/ | UI 相关业务逻辑、状态管理;不持有 View | 依赖 Service |
| Presentation/Page | {Feature}/Presentation/Page/ | UIViewController、UIView,只消费 ViewModel 状态 | 依赖 ViewModel |
自上而下单向依赖:Page → ViewModel → Service → Repository → Api / DTO。严禁反向依赖或跨层跳跃(例如 Page 直接调用 Repository)。
Feature A 需要 Feature B 的能力时,通过 B 暴露的 Service 协议 + Di 注册获取,不要直接 import B 的内部类型。
即使某层当下为空,也先建好目录。保证所有 Feature 结构对称,方便跨模块阅读。
所有网络返回 / 持久化模型统一放 {Feature}/Domain/DTO/,不在 Api 或 Repository 里零散定义。
每个 Feature 有独立的 Di 注册入口,应用启动时由上层统一调用。不要在 ViewController 或 ViewModel 里手动 new Service / Repository。
核心原则:凡是业务逻辑(含业务规则、跨 Repository 编排、领域状态流转、业务字段计算),只能写在 {Feature}/Service/ 下。
严禁的反模式(以下命名在项目里出现即视为违规,代码评审必须打回):
XxxManager / XxxTool / XxxUtils / XxxHelper / XxxHandler 承载业务逻辑XxxLogic、XxxProcessor、XxxCenter 等自造分层Arch/ 或全局单例里共享(Arch/ 只放与业务无关的通用基础设施,如网络封装、缓存、日志、UI 基类)判断标准(写之前先自问):
| 这段代码是什么? | 应该放哪 |
|---|---|
| 业务规则、领域判断、多接口编排、状态流转 | {Feature}/Service/ |
| 纯数据读写(网络 + 缓存) | {Feature}/Repository/ |
| UI 状态 / View 驱动 | {Feature}/Presentation/ViewModel/ |
| 真正与业务无关的通用工具(字符串处理、日期格式化、颜色扩展等) | Arch/ 或 Swift extension |
一句话判定:只要方法里出现"业务"两个字能说得通("判断订单是否可取消"、"合并用户信息与权限"),它就属于 Service。出现 Manager / Tool / Utils / Helper 作为业务归宿的名字时,先停下,把它挪到 Service。
核心原则:UI 层(Presentation)不得硬编码业务数据。任何"展示给用户的业务内容"(菜单项、条目、分段、入口、文案、图标等)都必须由 Service 构建 → ViewModel 暴露数据源 → UI 循环渲染。"界面上有 N 条"这个 N,永远来自 data.count,绝不写死。
严禁的反模式(出现即违规):
UIButton IBOutlet,后续加第三个要改 xib/代码if index == 0 { ... } else if index == 1 { ... } 式按位置写死分支numberOfRowsInSection 返回字面量 return 2正确做法:
[Item] 数组(可能来自后端配置、灰度、地区定制、枚举生成),ViewModel 转为数据源,View 循环渲染// ❌ 硬编码 2 个菜单项
final class ProfileViewController: UIViewController {
private let orderButton = UIButton(...) // "我的订单"
private let couponButton = UIButton(...) // "优惠券"
// 加第三项 → 改 xib、加 outlet、调布局
}
// ❌ TableView 按位置写死分支
func tableView(_ tv: UITableView, numberOfRowsInSection s: Int) -> Int { 2 }
func tableView(_ tv: UITableView, cellForRowAt ip: IndexPath) -> UITableViewCell {
if ip.row == 0 { /* 订单 */ } else { /* 优惠券 */ }
}
// ✅ 数据驱动:Service 定义 → ViewModel 暴露 → UI 绑定
struct ProfileMenuItem {
let id: String
let title: String
let icon: UIImage
let action: ProfileAction
}
protocol ProfileService {
/// 返回个人中心菜单项;顺序即展示顺序,可由后端配置或灰度动态下发。
func fetchMenuItems() async -> [ProfileMenuItem]
}
final class ProfileViewModel {
/// 菜单数据源;UI 只需绑定 count 与每项内容,无需感知"有几项"。
private(set) var menuItems: [ProfileMenuItem] = []
func load() async { menuItems = await service.fetchMenuItems() }
}
// View 层只做 items.count / items[i] 的循环渲染 —— 未来增减、重排、远程下发都不改 UI
判断边界:
| 场景 | 正确归宿 |
|---|---|
| 菜单项 / Tab / 分段 的数量与内容 | Service 动态返回,ViewModel 转数据源 |
| 业务文案、图标、跳转 action | 随数据结构一起从 Service 获取 |
| 固定的 app 级文字(导航栏标题、通用按钮文案) | Localizable.strings(仍算"配置"而非硬编码) |
| 布局骨架(搜索栏 + 列表这类容器结构) | 可以写死 View 层级,但其中 cell / section 仍走数据驱动 |
| 业务 KV 常量(订单状态、支付方式枚举值) | Domain/ 里的 enum,不是 UI 层魔法值 |
自检触发器:写代码时只要你在 VC/View 里出现以下任意一个,立刻停下挪到 Service:
return 2, maxItems: 3)switch / if-else 分支处理不同条目["订单", "优惠券", "地址"] 之类Api / Domain/DTO / Repository / Service / Di / Presentation(ViewModel + Page)?Domain/DTO/,未散落到 Api/Repository?Manager / Tool / Utils / Helper / Logic / Processor / Center 等自造业务分层?Arch/ 下无业务代码?return 2 式字面量条数、按索引的 if/switch、固定个数 IBOutlet 对应"入口"等反模式?