Files
photo-live/apps/mobile/ios/README-ios.md
T
浅念 42e1c2e34b feat(mobile): ios background upload bridge (compile-gated at G3)
Swift 侧(协议与 Android UploadPlugin.kt 逐字一致,编译/真机留 G3):
- UploadBridge.swift:通道/方法/事件/失败码常量 + registerTask 参数解析
  (镜像 UploadProtocol.kt)
- UploadEventBus.swift:单调 seq + 终态 ack 缓存重放(镜像
  UploadEventBus.kt),iOS 增强——未 ack 终态落盘(系统后台拉起投递的
  回调在进程重启后同样重放,技术方案 §6.2 持久化原生回调)
- UploadTaskStore.swift:taskId→URLSession taskIdentifier 映射 plist 持久化
  (Application Support;对应 Android WorkManager 自持映射)
- UploadPlugin.swift:URLSession background
  (com.photolive.photo_live.uploads)+ delegate 失败分类(2xx→completed+etag
  / 403→needsRenew / 4xx→HTTP_4XX / 网络→重试上限后 NETWORK,镜像
  UploadPutExecutor/UploadWorker)、原生重试(5 次上限、10s 指数退避)、
  进度 500ms 节流、queryTask/cancelTask/ackEvents、静默取消
- AppDelegate.swift:implicit engine 通道注册 + 启动重建会话 +
  handleEventsForBackgroundURLSession completionHandler 暂存
- Info.plist:UIBackgroundModes 有意不声明(background URLSession 无需;
  计划措辞按技术方案 §6.2 修正,见 README-ios.md)
- project.pbxproj 登记新 Swift 文件;README-ios.md(Xcode 步骤 + G3 验证
  清单:实际文件上传/持久化回调窗口/服务端补偿/重启重联校准/强制退出/
  低电量延迟/100 张批量)

Dart:NativeTransport 选择矩阵并入 iOS(Platform.isAndroid || isIOS,
FORCE_DART_TRANSPORT 覆盖不变)——协议两侧一致,无新通道代码。

flutter analyze 0 issue;flutter test 219 全绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-19 03:42:49 +08:00

98 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 侧仅作校准
参考,不依赖具体枚举。