From c95e80df9c88b7ea2cda1a0da6907ac4fda6ae66 Mon Sep 17 00:00:00 2001 From: joywayer Date: Mon, 22 Jun 2026 04:23:52 +0800 Subject: [PATCH] =?UTF-8?q?Phase=201.16=EF=BC=9ASceneDelegate=20=E6=8E=A5?= =?UTF-8?q?=20WebContainer=20+=20=E9=80=80=E5=BD=B9=20RootViewController?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SceneDelegate.scene(_:willConnectTo:options:) 中 rootViewController 从 M0 占位 RootViewController() 改为 WebContainerViewController() - 删除 ylgamehall/RootViewController.swift(M0 + Phase 1.2–1.13 烟雾测试 整体退役);所有 print 验证记录在 git log,留着是死代码 - File System Synchronized Group 自动适应文件删除,无需改 pbxproj - BuildProject 通过 至此 Phase 1.16 完成;启动链路: SceneDelegate → WebContainerViewController.viewDidLoad → setupBridgedWebView + setupSplash + registerBridgeHandlers → runBootPipeline (ensureReady → fetch → resolve → upgrade → loadFileURL) → didFinish 淡出 splash Plan 进度已勾选(§5 Phase 1.16 + §8) Co-Authored-By: Claude Opus 4.7 --- docs/Development-Plan.md | 8 +- ylgamehall/RootViewController.swift | 202 ---------------------------- ylgamehall/SceneDelegate.swift | 2 +- 3 files changed, 6 insertions(+), 206 deletions(-) delete mode 100644 ylgamehall/RootViewController.swift diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index 2d043ee..50829a0 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -312,8 +312,10 @@ Contract Design Plan(本文档) - 入参忽略(参考 msext `RootVC.m:1816-1818`:`time` 参数实际不读取) - 副作用:`AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)`;responseCallback:`.string("vibrator")` - `WebContainerViewController.viewDidLoad` 新增 `registerBridgeHandlers()` 单一聚合点,Phase 2+ 在此追加。BuildProject 通过 -- [ ] **1.16** `SceneDelegate` → `WebContainerViewController` 作 rootViewController - - 替换 M0 占位 `RootViewController` +- [x] **1.16** `SceneDelegate` → `WebContainerViewController` 作 rootViewController + - 替换 M0 占位 `RootViewController` + 删除 `ylgamehall/RootViewController.swift`(M0 烟雾测试堆栈整体退役,所有 1.2–1.13 print 验证记录在 git log,留着是死代码) + - File System Synchronized Group 自动适应文件删除,无需改 pbxproj + - BuildProject 通过 - [ ] **1.17** 把渠道目录里临时填入的 demo 值 + 一个 demo H5 跑通 / 真机验证完整 1.10-1.16 链路 #### 验收 @@ -808,7 +810,7 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin - [x] 1.14.d WebContainerViewController(SplashOverlay + 16:9 letterbox + 启动流水线 + 三种 alert) - [x] 1.14.e WebView didFinish 后 0.3s 淡出 splash + removeFromSuperview - [x] 1.15 VibratorHandler(单次振动,AudioServicesPlaySystemSound + responseCallback "vibrator") -- [ ] 1.16 SceneDelegate → WebContainerViewController +- [x] 1.16 SceneDelegate → WebContainerViewController(删除 M0 占位 RootViewController.swift) - [ ] 1.17 Demo H5 联调 + 真机验证升级路径 ### Phase 2 大厅简单 handler + 反向 callback diff --git a/ylgamehall/RootViewController.swift b/ylgamehall/RootViewController.swift deleted file mode 100644 index ddcb93b..0000000 --- a/ylgamehall/RootViewController.swift +++ /dev/null @@ -1,202 +0,0 @@ -// -// RootViewController.swift -// ylgamehall -// -// M0 阶段临时根控制器,后续将被 Coordinator + LobbyViewController 替换。 -// - -import UIKit - -final class RootViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .black - - // Phase 1.2 烟雾测试:打印 ChannelConfig.plist 的 11 项渠道注入值 - let cfg = BundleConfig.shared - print(""" - [BundleConfig] 渠道注入读取结果: - qiniuDomain = \(cfg.qiniuDomain) - gameId = \(cfg.gameId) - channel = \(cfg.channel) - gameDir = \(cfg.gameDir) - gameStart = \(cfg.gameStart) - gameConfig = \(cfg.gameConfig) - market = \(cfg.market) - agent = \(cfg.agent) - appVersion = \(cfg.appVersion) - other = "\(cfg.other)" - appleConfig = "\(cfg.appleConfig)" - """) - - // Phase 1.3 烟雾测试:打印 SandboxPaths 计算路径 - print(""" - [SandboxPaths] 路径计算结果: - caches = \(SandboxPaths.caches.path) - documents = \(SandboxPaths.documents.path) - bundle = \(SandboxPaths.bundle.path) - lobbyRoot = \(SandboxPaths.lobbyRoot.path) - lobbyIndex = \(SandboxPaths.lobbyIndex.path) - lobbyVersionXML = \(SandboxPaths.lobbyVersionXML.path) - """) - - // Phase 1.5 烟雾测试:触发 ResourceUnzipper,确认 lobby index.html 存在 - Task { - let start = Date() - do { - try await ResourceUnzipper.shared.ensureReady() - let elapsed = Date().timeIntervalSince(start) - let exists = FileManager.default.fileExists(atPath: SandboxPaths.lobbyIndex.path) - print(""" - [ResourceUnzipper] ensureReady 完成(耗时 \(String(format: "%.3f", elapsed))s) - lobbyIndex 存在: \(exists) - """) - } catch { - print("[ResourceUnzipper] ERROR: \(error)") - } - } - - // Phase 1.8 烟雾测试:确认 WebViewJavascriptBridge.js 已入 Bundle 且可读 - // 注:synchronized group 会把 ylgamehall/Resources/JS/ 子目录扁平化为 bundle 根, - // 所以无需 subdirectory 参数。 - if let url = Bundle.main.url(forResource: "WebViewJavascriptBridge", - withExtension: "js"), - let content = try? String(contentsOf: url, encoding: .utf8) { - print(""" - [WVJB.js] 已就绪 - path = \(url.path) - bytes = \(content.utf8.count) - head = \(content.prefix(60).replacingOccurrences(of: "\n", with: " ⏎ "))… - """) - } else { - print("[WVJB.js] ERROR: 找不到 Bundle 内 JS/WebViewJavascriptBridge.js") - } - - // Phase 1.10 烟雾测试:拉远端渠道配置 .txt(含 cache-busting + 短文本响应识别) - Task { - let start = Date() - do { - let outcome = try await RemoteConfigClient.shared.fetch() - let elapsed = Date().timeIntervalSince(start) - switch outcome { - case .parsed(let cfg): - print(""" - [RemoteConfigClient] 拉取成功 .parsed(耗时 \(String(format: "%.3f", elapsed))s) - showmessage = \(cfg.showmessage ?? "") - agentlist count = \(cfg.agentlist?.count ?? 0) - """) - if let first = cfg.agentlist?.first { - print(""" - agent[0].agentid = \(first.agentid ?? "") - agent[0].appVersion = \(first.appVersion ?? "") - agent[0].gameVersion = \(first.gameVersion ?? "") - agent[0].channellist = \(first.channellist?.count ?? 0) 个 - agent[0].gamelist = \(first.gamelist?.count ?? 0) 个 - """) - } - - // Phase 1.11 烟雾测试:chulishengji 双子树合并 - let bc = BundleConfig.shared - let resolved = VersionResolver.resolve( - config: cfg, - agentId: bc.agent, - channelId: bc.channel, - marketId: bc.market, - gameId: bc.gameId - ) - print(""" - [VersionResolver] chulishengji 双子树合并结果: - appVersion = \(resolved.appVersion) - appDownload = \(resolved.appDownload ?? "") - gameVersion = \(resolved.gameVersion) - gameZip = \(resolved.gameZip ?? "") - showmessage = "\(resolved.showmessage ?? "")" - """) - - // Phase 1.12 烟雾测试:读本地版本号 + 与远端对比 - let localApp = LocalVersionReader.localAppVersion - let localGame = LocalVersionReader.localGameVersion - let needIPAUpgrade = resolved.appVersion > localApp - let needZipUpgrade = resolved.gameVersion > localGame - print(""" - [LocalVersionReader] 本地 vs 远端对比: - 本地 appVersion = \(localApp)(来自 ChannelConfig.plist) - 远端 appVersion = \(resolved.appVersion) - → IPA 升级 = \(needIPAUpgrade ? "需要" : "不需要") - 本地 gameVersion = \(localGame)(来自 version.xml /game/version@value) - 远端 gameVersion = \(resolved.gameVersion) - → H5 zip 升级 = \(needZipUpgrade ? "需要" : "不需要") - """) - - // Phase 1.13 / 1.14.c 烟雾测试:触发 LobbyZipUpgrader(如果需要升级), - // 并打印 progress 回调(每 10% 报一次,避免刷屏) - let upgradeStart = Date() - do { - let lastReported = ProgressTicker() - let outcome = try await LobbyZipUpgrader.shared.upgradeIfNeeded( - resolved: resolved, - onProgress: { p in - lastReported.tickIfPassed(p) { reported in - print(String(format: "[LobbyZipUpgrader] 下载进度 %3d%%", Int(reported * 100))) - } - } - ) - let elapsed = Date().timeIntervalSince(upgradeStart) - switch outcome { - case .noop: - print(""" - [LobbyZipUpgrader] 无需升级,跳过(耗时 \(String(format: "%.3f", elapsed))s) - """) - case .upgraded(let from, let to): - let newLocalGame = LocalVersionReader.localGameVersion - print(""" - [LobbyZipUpgrader] 升级完成 \(from) → \(to)(耗时 \(String(format: "%.3f", elapsed))s) - 重新读 version.xml: localGameVersion = \(newLocalGame) - lobbyIndex 存在: \(FileManager.default.fileExists(atPath: SandboxPaths.lobbyIndex.path)) - """) - } - } catch { - print("[LobbyZipUpgrader] ERROR: \(error)") - } - case .shortText(let msg): - print(""" - [RemoteConfigClient] 拉到短文本响应(耗时 \(String(format: "%.3f", elapsed))s) - 运营杀手锏:应当弹窗显示并停止重试 - 内容 = "\(msg)" - """) - } - } catch { - print("[RemoteConfigClient] ERROR: \(error)") - } - } - } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - .landscape - } - - override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { - .landscapeRight - } - - override var prefersStatusBarHidden: Bool { false } -} - -/// 仅供 Phase 1.14.c 烟雾测试使用:按 10% 阶梯节流 progress 打印,避免几百行刷屏。 -/// `@unchecked Sendable`:状态仅一个 Int 阈值,用 NSLock 保护写入。 -private final class ProgressTicker: @unchecked Sendable { - private let lock = NSLock() - private var lastBucket: Int = -1 - - func tickIfPassed(_ progress: Double, action: (Double) -> Void) { - let bucket = Int(progress * 10) - lock.lock() - let shouldFire = bucket > lastBucket - if shouldFire { lastBucket = bucket } - lock.unlock() - if shouldFire { - action(Double(bucket) / 10.0) - } - } -} diff --git a/ylgamehall/SceneDelegate.swift b/ylgamehall/SceneDelegate.swift index d57b0c9..7f4eb45 100644 --- a/ylgamehall/SceneDelegate.swift +++ b/ylgamehall/SceneDelegate.swift @@ -14,7 +14,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } let window = UIWindow(windowScene: windowScene) - window.rootViewController = RootViewController() + window.rootViewController = WebContainerViewController() self.window = window window.makeKeyAndVisible() }