iOS 即时通讯(IM)聊天 App。标准 Xcode 工程,UIKit + Storyboard。
- 主语言:Objective-C(ARC)
- 备用:Swift(新模块/混编,通过 Bridging Header)
- UI:UIKit,纯代码约束为主,Storyboard 仅启动屏/简单页
- 依赖管理:待定(CocoaPods 或 SPM)
- 通信方案:待定(自建 WebSocket / 三方 IM SDK)
IMProgram/— 主工程源码IMProgramTests/— 单元测试IMProgramUITests/— UI 测试IMProgram.xcodeproj— 工程文件
- 每次开始主要回复前,先读
current_task.md恢复上下文,改动后更新它。 current_task.md是"活快照",不是流水账:固定四节(当前焦点 / 下一步 / 已知坑·限制 / 关联工程·常用命令),就地覆盖,禁止往下追加Status ②③④…新块。需要留痕的历史交给git log与current_task.archive.md(只读归档)。逐功能×端状态一律只写../IMServer/docs/CLIENT_PARITY.md(唯一来源),别处不复述 ✅。- 遵循
CODING_STYLE.md:类前缀IM、4 空格缩进、网络/IO 必须有错误恢复。 - 新增或修改任何 UI 前必须读取
docs/UI_COLOR.md;颜色/字号/圆角使用IMTheme, 用户外观偏好使用IMAppearance,禁止业务页面直接持久化外观或新增魔法颜色。 - 架构设计见
ARCHITECTURE.md;通信协议见../IMServer/docs/PROTOCOL.md。 - 日志实现见
docs/LOGGING.md,跨端共同契约见../IMServer/docs/LOGGING.md;新增日志必须走IMLog.h。 - 今后新增的业务/技术 Markdown 文档一律放入
docs/。根目录仅保留 README、AGENTS/CLAUDE、current_task.md及既有工程入口规范;不为整理目录而移动这些入口文件。 - 提交信息格式:
类型(模块): 描述。
动手前(Read,不靠记忆):
- 改客户端代码前,先 Read
CODING_STYLE.md与ARCHITECTURE.md;涉及协议字段再 Read../IMServer/docs/PROTOCOL.md。
声明「完成」前必须全部满足,并在回复中贴出 ./scripts/test.sh 的输出:
- 新功能配套测试用例(
IMProgramTests/的 XCTest),纳入回归。 ./scripts/test.sh全绿(体量门禁 + 编译 +IMProgramTests单测)——这是唯一入口,别手拼 xcodebuild 命令行,理由见「构建 / 测试」一节。- 改代码途中想快速过一遍编译:
BUILD_ONLY=1 ./scripts/test.sh(不碰模拟器)。但声明「完成」前必须去掉BUILD_ONLY再跑一次。 - 只跑某个类/某条用例:
ONLY=IMResendPolicyTests ./scripts/test.sh/ONLY=IMResendPolicyTests/testXxx ./scripts/test.sh。
- 改代码途中想快速过一遍编译:
- 更新
current_task.md。 - 更新
../IMServer/docs/CLIENT_PARITY.md对应单元格(功能×端状态的唯一来源)。 - 端对齐扫一遍:凡声明"某功能完成/对齐 Web",先按 CLIENT_PARITY 逐行 diff iOS↔Web——Web ✅ 而 iOS ⬜ 的就是缺口,要么补上、要么在回复里点名为已知缺口。("↓N 跳转"曾因没做这步而漏掉。)
- 给出真机验证清单:列出本次需在真机上肉眼确认的功能点,交用户手测;明确说清「没做什么 / 已知限制 / TODO」,不假装完成。
主动建议(不必用户开口):
- 完成较大功能后,建议跑
/code-review自审找 bug。 - 触及鉴权 / 加密 / E2E / 敏感数据时,建议跑
/security-review。
- 已用 CocoaPods(FMDB):打开/构建一律用
IMProgram.xcworkspace,不再用.xcodeproj。新机器先cd IMProgram && pod install。 - 唯一入口:
./scripts/test.sh(与后端IMServer/scripts/test.sh对称)。./scripts/test.sh # 体量门禁 + 编译 + IMProgramTests 单测 BUILD_ONLY=1 ./scripts/test.sh # 只编译(不碰模拟器,最快) ONLY=IMResendPolicyTests ./scripts/test.sh # 只跑一个类(排查偶发失败用)
- 不要手拼
xcodebuild命令行——2026-08-31 之前每次都拼得不一样,反复踩三个坑,脚本已把它们写死:- 不能跑整个 scheme:它带着
IMProgramUITests,testExample一条 135s、testLaunch37s(要真启动 App 跑交互);而 313 条单测加起来不到 1 分钟。脚本写死-only-testing:IMProgramTests。UI 测试要跑请单独手动跑。 - 必须关并行:Xcode 默认按 target 克隆模拟器并行跑,一台机器上自己跟自己抢 CPU,会把时序敏感的用例压出偶发失败(
IMMediaPlaceholderTests那条,见current_task.md「已知坑」)。脚本写死-parallel-testing-enabled NO。 - 失败原因不在纯文本日志里:xcodebuild 只给一句
** TEST FAILED **,断言原文在.xcresult里。脚本固定-resultBundlePath并用xcresulttool把「哪条用例 + 断言原文」直接打出来。
- 另:固定
-derivedDataPath build/DerivedData复用增量产物;模拟器自动挑最新 iOS 的 iPhone(写死名字会在换 Xcode/换机后报 destination 找不到,那种失败看起来像"测试挂了",白费排查时间)。要指定:IM_SIM=<udid|名字>。
- 不能跑整个 scheme:它带着
- 完整日志与结果包落在
build/(已 gitignore),按跑次带 PID:build/xcodebuild-test.<pid>.log/build/TestResults.<pid>.xcresult;build/xcodebuild-test.log与build/TestResults.xcresult是指向最近一次的软链。 带 PID 是因为本脚本会被并发跑起来(同时开两个会话就够了),共用固定路径时后起的那个 会删掉前一个正在写的结果包 —— 表现是「全绿却报 TEST FAILED」。脚本每次启动会清掉 已退出跑次的产物(正在跑的另一个会话原样保留)。 DerivedData 仍是共享的(增量编译产物,Xcode 自己加锁排队);模拟器也是共享的, 真要并发请IM_SIM=<另一台 udid>各跑各的。 - Podfile 已关
ENABLE_USER_SCRIPT_SANDBOXING(post_install),避免 Pods 资源拷贝被沙盒拒写。