# suixinkan

## 项目概览

**Swift + UIKit** 工程，**MVVM** 架构。iOS 版 **随心瞰商家版**，与 Android **ZhiFlyFollow** 功能对齐——以 Android 工程为参考同步实现业务能力，UI 按 UIKit 习惯编写，不逐行翻译 Compose。

**Android 参考：** `/Users/hanqiu/Desktop/android_project/zhiflyfollow`（`com.zhifly.follow`）  
**文档：** `CLAUDE.md`、`docs/home/`、`docs/排队叫号业务流程梳理.md`  
**后端：** 生产 `api.zhifly.cn`，测试 `api-test.zhifly.cn`

**主要功能域：** 首页/订单/数据/我的 Tab；登录注册与实名认证；订单/任务/核销；相册与云存储；直播与钱包；景区排队叫号、位置上报；消息、设置、扫码、语音通话等。

**同步约定：** 接口与业务流程以 Android `NetworkApi` / DTO / ViewModel 为准；UI 用 SnapKit 独立设计；OTG 相机、DJI 飞控、厂商推送等 Android 专属能力按需评估。

## 平台与设备

iOS 16+ · 仅 iPhone · 仅竖屏。不做 iPad / 横屏 / 低版本 API 适配。

## 架构（MVVM）

| 层级 | 职责 |
|------|------|
| **View** | `UIViewController` / 自定义 `UIView`，UI 展示与交互，不含业务逻辑 |
| **ViewModel** | 业务逻辑与状态；通过方法、delegate 或明确回调与 View 通信 |
| **Model** | 数据模型、网络/本地数据源 |

- 新增功能：对应 Model + ViewModel + ViewController，同步单元测试
- ViewModel 不依赖 UIKit 视图类型

## 开发风格

**命令式 UIKit**，不用 `@Observable` / `ObservableObject` / `@Published`、不用 Combine 驱动 UI、不堆泛化 `onChanged` 回调。

- 在明确时机（点击、网络回调、页面出现）主动刷新 UI（`apply(snapshot)` 或更新子视图）
- 通信用 delegate、target-action 或语义明确的单一回调
- 对照 Android **行为** 用 UIKit 重写，不翻译 Compose 语法
- ViewModel 属性供读取，UI 更新由 View 在回调中主动触发

## Swift 并发与 Actor

工程 **Default Actor Isolation** 配置为 **`nonisolated`**（`suixinkan` 与 `suixinkanTests` 均为 `SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated`）。新类型默认不隐式绑定 `@MainActor`，与命令式 UIKit + 显式刷新风格一致。

| Target | Default Actor Isolation |
|--------|-------------------------|
| `suixinkan` | `nonisolated` |
| `suixinkanTests` | `nonisolated` |

编写约定：

- **ViewModel / Model**：保持 `nonisolated`，不依赖默认 MainActor；纯逻辑与校验可用 `nonisolated static`。
- **需要主线程的类型**：在类型上**显式**标注 `@MainActor`（如 `APIClient`、`AuthAPI`、`ProfileAPI`、`NetworkServices`、`OSSUploadService`、`AppRouter`）。
- **UI 更新**：在 `UIViewController` 内通过 `Task { @MainActor in ... }` 或已在主线程的回调中刷新 UI，不假设 ViewModel 调用方一定在主 Actor；`onStateChange` 回调应切回主线程再改 UI。
- **网络与可发送类型**：请求/环境等跨边界类型保持 `nonisolated` + `Sendable`（如 `APIRequest`），避免无意引入 actor 隔离冲突。
- **单元测试**：target 默认为 `nonisolated`；`MockURLSession` 等替身保持 `nonisolated`。需要构造 `@MainActor` 类型（`APIClient`、`AuthAPI` 等）的测试类应显式标 `@MainActor`。

新增代码不要为「凑隔离」给 ViewModel 或普通 model 加 `@MainActor`；仅 UI 入口、共享网络单例等确需主线程协调处显式标注。

## 代码注释

`class` / `struct` / `enum`、协议及公开 API 必须写 `///` 文档注释，说明职责与用途；复杂内部逻辑按需补充。不写废话注释、不写过时注释。

## 技术栈与库

Swift · UIKit · SnapKit（代码布局，约束写在 `setupUI()` / `setupConstraints()`）· Kingfisher（`kf.setImage(with:)` 加载网络图，列表利用缓存）

## 列表展示

- 简单列表 → `UITableView`；复杂列表（网格、卡片、不等高）→ `UICollectionView` + Compositional Layout
- 数据更新用 Diffable Data Source + `apply(_:animatingDifferences: true)`，Item 遵循 `Hashable`，避免 `reloadData()`
- ViewModel 提供数据，View 构建 snapshot 并 apply

## 单元测试

每增一功能必须附带单元测试且全部通过，与功能同次提交。单元测试必须在已连接的 iPhone 真机上运行，不使用模拟器；支持使用与开发机处于同一 Wi-Fi 下并已启用无线连接的真机。可在 Xcode 中选择真机后执行 `Cmd + U`，或使用 `xcodebuild test -destination 'platform=iOS,id=<设备 UDID>'`。

| 层级 | 优先级 |
|------|--------|
| ViewModel / Model | ✅ 优先 |
| View | 按需 |

测试放 `suixinkanTests/`，独立可重复，mock 网络与外部依赖。

## 开发注意

- SnapKit 布局、Kingfisher 图片、Diffable 列表带动画
- 以 Android 工程为功能参考，命令式 UIKit，不用 Observable / Combine
- 新增功能必测、类型与公开 API 必注释、保持简洁
