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

6.3 KiB
Raw Blame 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 侧仅作校准 参考,不依赖具体枚举。