Files
photo-live/apps/mobile/ios/README-ios.md
T

98 lines
6.3 KiB
Markdown
Raw Normal View History

# iOS 桥接说明(P3 Task 8——编译/真机验证留 G3)
> 本目录的 Swift 桥接代码在 Windows 开发机上完成(无 macOS,未编译)。
> 首次 macOS 构建前请通读本文档;所有「G3 验证」条目对应
> `docs/03-技术实施方案.md` §6.2/§6.4。
## 代码清单
| 文件 | 职责 | Android 对应 |
|---|---|---|
| `Runner/UploadBridge.swift` | 协议常量 + registerTask 参数解析(纯函数) | `UploadProtocol.kt` |
| `Runner/UploadEventBus.swift` | 事件总线:单调 seq、终态 ack 缓存重放(**iOS 增强落盘**) | `UploadEventBus.kt` |
| `Runner/UploadTaskStore.swift` | taskId→系统任务映射持久化(Application Support plist) | WorkManager 自持映射 |
| `Runner/UploadPlugin.swift` | MethodChannel/EventChannel 注册 + URLSession background 会话 + delegate 失败分类 + 原生重试 | `UploadPlugin.kt` + `UploadWorker.kt` + `UploadPutExecutor.kt` |
| `Runner/AppDelegate.swift` | 通道注册(implicit engine)+ 启动重建会话 + 后台拉起 completionHandler 暂存 | `MainActivity.kt` |
协议(通道名 `photolive/uploads`、事件流 `photolive/uploads/events`、
方法/事件名、失败码)与 Android 侧**逐字一致**;Dart 侧同一
`NativeTransport`(`lib/features/uploads/native_transport.dart`)驱动两平台,
无协议分歧登记项。
## Background Modes(计划偏差修正)
**有意不声明 `UIBackgroundModes`**(Info.plist 内有注释标记):上传采用
background URLSession(identifier `com.photolive.photo_live.uploads`),
传输与完成回调由系统调度,`fetch`/`processing` 等模式均非必需;计划原文
的「backgroundModes(fetch/processing 按文档)」按技术方案 §6.2 与 Apple
文档修正(多声明反而引入 App Store 审核 scrutiny)。无需 Push、无需
任何 Capability 开关。
## Xcode 步骤(G3 首次构建)
1. macOS 上 `source scripts/flutter-env.sh` 的等价环境(Flutter 3.44+),
`flutter precache --ios` 后 `cd apps/mobile && flutter pub get`。
2. 打开 `apps/mobile/ios/Runner.xcworkspace`。
3. Runner target → Signing & Capabilities:选择开发团队(个人免费证书
即可真机调试;无需勾选任何额外 Capability)。
4. `flutter run -d <deviceId>` 或 Xcode 直接 Run;新 Swift 文件已登记于
`project.pbxproj`(4 个 `Upload*.swift`),无需手动拖入。
5. 首次编译预期**零改动通过**;若 Swift 语法/API 有出入(未编译验证的
风险),修复后把差异登记进验收记录偏差表。
### 开发联调网络
Info.plist 已含 `NSAppTransportSecurity.NSAllowsLocalNetworking`:模拟器/
真机可访问宿主回环(`http://<宿主IP>:8080` 等)的本地 cn-api + MinIO;
生产仍要求 HTTPS。
## G3 验证清单(技术方案 §6.2 要点)
按序执行并记录(设备/系统版本/文件大小/成功数/恢复行为/重复记录数):
- [ ] **实际文件上传**:真实照片文件(非桩)经完整链路(presign →
URLSession PUT → complete)进入服务端;App 退后台/锁屏传输继续。
- [ ] **持久化原生回调**:上传完成时 App 在后台被系统拉起——确认终态
事件已落盘(`upload-unacked-events.plist`)并在 App 回前台
EventChannel 重听后重放、Dart ack 后清除;**在系统允许的执行窗口
内完成确认**(complete 调用),超窗场景由服务端对象通知/补偿扫描
推动发布(§5.6),App 侧不得标失败重传覆盖。
- [ ] **重启重联校准**:上传中杀进程 → 重新启动 → 会话重联
(`getAllTasks` + taskIdentifier 反查 taskId)、队列状态校准、
未完成任务续传(映射文件 `upload-task-map.plist`)。
- [ ] **强制退出**:用户强制退出后系统**不会**自动重启 App 承接会话
事件——需下次启动恢复(§6.2 明文语义,UI 不承诺后台完成)。
- [ ] **低电量/调度延迟**:低电量模式 + 断网恢复场景,任务挂起而非
失败;UI 显示等待状态(`waiting_network`),不承诺固定完成时间。
- [ ] **凭证过期**:presign 403 → `needsRenew` → Dart renew 换 URL 重注册
(含长时间后台后 renew 的可达性——§5.2 技术验证重点)。
- [ ] **原生重试上限**:断网/5xx 下原生侧重试 5 次(指数退避 10s 起)
后 `failed{NETWORK|SERVER_ERROR}`;退避定时器在 App 挂起期间暂停、
唤醒补跑(G3 确认行为符合预期)。
- [ ] **进度节流**:进度事件 500ms 节流 + 终值必发(与 Android 每 64KB
发事件的差异——Dart 侧 `onProgress` 语义不受影响)。
- [ ] **批量样本**:批量 100 张真实照片(§6.4 起始样本),覆盖前台切
后台、锁屏 ≥15min、网络中断恢复、强制退出重开、源文件缺失
(`failed{SOURCE_MISSING}`)、存储不足;记录重复记录数(幂等
clientTaskId 下应为 0)。
- [ ] **HEIC 真机样本**(§5.4,与 G2 清单合并执行)。
- [ ] 补充单测(可选):`UploadBridge.parseRegisterSpec` /
`UploadEventBus` 重放语义的 XCTest(Android 侧有 JVM 对应单测)。
## 与 Android 实现的已知差异(协议外,行为层)
1. **终态缓存落盘**:iOS 侧未 ack 终态事件持久化(系统可能后台拉起
App 投递回调而 Dart 引擎未运行);Android 侧内存缓存即可(WorkManager
自持任务,进程重启后 worker 重跑再发)。
2. **重试宿主**:iOS 由插件层重建 upload task(10s 指数退避,上限 5 次
含首发);Android 为 WorkManager `Result.retry` 同参数。App 挂起时
iOS 退避定时器暂停、唤醒补跑。
3. **进度节流**:iOS 在事件层 500ms 节流(终值必发);Android 每写
64KB 发事件、仅通知刷新节流 500ms。
4. **取消语义**:两侧均为「Dart 先行置 cancelled、原生静默停任务、无
终态事件」;iOS 额外经 `getAllTasks` 兜底取消未重挂任务。
5. **queryTask 状态枚举**:iOS 以 URLSessionTask.state 映射
RUNNING/ENQUEUED/CANCELLED(终态后条目清理,返回 null = 未知);
Android 为 WorkInfo 原名(SUCCEEDED/FAILED 可查)。Dart 侧仅作校准
参考,不依赖具体枚举。