Files
youle_app_ios_v2/ylgamehall/RootViewController.swift
T
joywayerandClaude Opus 4.7 f3c66b670d Phase 1.14.c:LobbyZipUpgrader 加 0…1 progress 回调
- upgradeIfNeeded 新增 onProgress: @escaping @Sendable (Double) -> Void 参数,
  默认 no-op 兼容仅做版本对比的烟雾测试
- 新增文件内私有 DownloadProgressDelegate(URLSessionDownloadDelegate
  + @unchecked Sendable),在 didWriteData 回调里把 totalBytesWritten /
  totalBytesExpectedToWrite 换算成 0…1 上报,clamp 到 [0,1]
- 通过 session.download(from:delegate:) 注入;下载完成后兜底报 1.0
  (部分 server 不发 Content-Length 或末尾片段晚到,避免进度条停在 99%)
- RootViewController 烟雾测试用 ProgressTicker 把回调节流到 10% 阶梯打印,
  避免几百行刷屏(NSLock 保护单 Int 状态,@unchecked Sendable)
- BuildProject 通过

Plan 进度已勾选(§5 Phase 1.14.c + §8)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 02:46:11 +08:00

203 lines
9.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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 ?? "<nil>")
agentlist count = \(cfg.agentlist?.count ?? 0)
""")
if let first = cfg.agentlist?.first {
print("""
agent[0].agentid = \(first.agentid ?? "<nil>")
agent[0].appVersion = \(first.appVersion ?? "<nil>")
agent[0].gameVersion = \(first.gameVersion ?? "<nil>")
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 ?? "<nil>")
gameVersion = \(resolved.gameVersion)
gameZip = \(resolved.gameZip ?? "<nil>")
showmessage = "\(resolved.showmessage ?? "<nil>")"
""")
// 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)
}
}
}