42e1c2e34b
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>
98 lines
6.3 KiB
Markdown
98 lines
6.3 KiB
Markdown
# 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 侧仅作校准
|
||
参考,不依赖具体枚举。
|