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>
This commit is contained in:
浅念
2026-09-19 03:42:49 +08:00
parent 4e35bdc0b2
commit 42e1c2e34b
11 changed files with 1146 additions and 33 deletions
@@ -12,6 +12,10 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
7884E8812EC3CC0A00C636F3 /* UploadBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8802EC3CC0A00C636F3 /* UploadBridge.swift */; };
7884E8832EC3CC0B00C636F3 /* UploadEventBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8822EC3CC0B00C636F3 /* UploadEventBus.swift */; };
7884E8852EC3CC0C00C636F3 /* UploadTaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8842EC3CC0C00C636F3 /* UploadTaskStore.swift */; };
7884E8872EC3CC0D00C636F3 /* UploadPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8862EC3CC0D00C636F3 /* UploadPlugin.swift */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@@ -50,6 +54,10 @@
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
7884E8802EC3CC0A00C636F3 /* UploadBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadBridge.swift; sourceTree = "<group>"; };
7884E8822EC3CC0B00C636F3 /* UploadEventBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadEventBus.swift; sourceTree = "<group>"; };
7884E8842EC3CC0C00C636F3 /* UploadTaskStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadTaskStore.swift; sourceTree = "<group>"; };
7884E8862EC3CC0D00C636F3 /* UploadPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPlugin.swift; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
@@ -123,6 +131,10 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
7884E8802EC3CC0A00C636F3 /* UploadBridge.swift */,
7884E8822EC3CC0B00C636F3 /* UploadEventBus.swift */,
7884E8842EC3CC0C00C636F3 /* UploadTaskStore.swift */,
7884E8862EC3CC0D00C636F3 /* UploadPlugin.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
@@ -284,6 +296,10 @@
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
7884E8812EC3CC0A00C636F3 /* UploadBridge.swift in Sources */,
7884E8832EC3CC0B00C636F3 /* UploadEventBus.swift in Sources */,
7884E8852EC3CC0C00C636F3 /* UploadTaskStore.swift in Sources */,
7884E8872EC3CC0D00C636F3 /* UploadPlugin.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};