# 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 ` 或 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 侧仅作校准 参考,不依赖具体枚举。