启动期完成「ChannelConfig.plist 读 → SandboxPaths 算路径 →
gamehall.zip 解压」三步,lobbyIndex 真实存在于沙盒 Caches,
为 Phase 1.10 WebContainer file:// 加载就绪。
- 新增 ylgamehall/Source/Resource/ResourceUnzipper.swift
- public actor,全局单例 ResourceUnzipper.shared
- ensureReady() async throws:检查 lobbyVersionXML 不存在则调
FileManager.unzipItem (ZIPFoundation 扩展) 解压 Bundle 内
gamehall.zip 到 SandboxPaths.lobbyRoot
- UnzipError 类型化错误(bundleZipNotFound / unzipFailed)
- 幂等:已就绪直接 return,并发调用安全(actor 串行)
- 新增 ylgamehall/Resources/gamehall.zip:从 docs/res 拷一份入
Bundle(11.5 MB,2023-12 旧版;上线前由 H5 团队替换最新版,
ADR-002 已记录)
- Swift 6 严格并发适配:
- SandboxPaths:所有 static 成员标 nonisolated(无状态命名空间,
从 actor / 非 MainActor 上下文都能直接读 caches/documents/bundle
及 lobby 路径计算)
- BundleConfig:class 整体 nonisolated(Sendable + all-let 属性,
跨 actor 边界安全),允许 ResourceUnzipper actor 读 channel 配置
- RootViewController 补 ResourceUnzipper 烟雾测试 Task:
await ensureReady() 后打印耗时 + lobbyIndex 是否真实存在
- 真机验证(iOS Simulator):首次 1.266s 解压成功,二次启动 0s 跳过
57 lines
2.2 KiB
Swift
57 lines
2.2 KiB
Swift
//
|
|
// BundleConfig.swift
|
|
// ylgamehall
|
|
//
|
|
// 渠道注入配置:从 Bundle 内的 ChannelConfig.plist 读 11 个 string 值。
|
|
// 设计模式与契约见 docs/H5-Native-Implementation-Design.md §7 / ADR-007。
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// 渠道注入配置(11 项),由 `BundleConfig.shared` 在 App 启动时一次性加载,运行期不变。
|
|
///
|
|
/// 多渠道分发:构建后用 `plutil` 修改 `.app/ChannelConfig.plist` + 重签,
|
|
/// 不需要重新 Xcode build。详见 ADR-007。
|
|
nonisolated public final class BundleConfig: Sendable {
|
|
|
|
/// App 启动期默认读 main bundle 的 `ChannelConfig.plist`,业务代码统一通过此单例访问。
|
|
public static let shared = BundleConfig()
|
|
|
|
public let qiniuDomain: String
|
|
public let gameId: String
|
|
public let channel: String
|
|
public let gameDir: String
|
|
public let gameStart: String
|
|
public let gameConfig: String
|
|
public let market: String
|
|
public let agent: String
|
|
public let appVersion: String
|
|
public let other: String
|
|
public let appleConfig: String
|
|
|
|
/// 单测可注入任意 bundle 验证不同 plist fixture。
|
|
public init(bundle: Bundle = .main) {
|
|
let dict = Self.loadPlist(bundle: bundle)
|
|
qiniuDomain = dict["qiniudomain"] ?? ""
|
|
gameId = dict["gameid"] ?? ""
|
|
channel = dict["channel"] ?? ""
|
|
gameDir = dict["gamedir"] ?? ""
|
|
gameStart = dict["gamestart"] ?? ""
|
|
gameConfig = dict["gameconfig"] ?? ""
|
|
market = dict["market"] ?? ""
|
|
agent = dict["agent"] ?? ""
|
|
appVersion = dict["appversion"] ?? ""
|
|
other = dict["other"] ?? ""
|
|
appleConfig = dict["appleconfig"] ?? ""
|
|
}
|
|
|
|
private static func loadPlist(bundle: Bundle) -> [String: String] {
|
|
guard let url = bundle.url(forResource: "ChannelConfig", withExtension: "plist"),
|
|
let data = try? Data(contentsOf: url),
|
|
let plist = try? PropertyListSerialization.propertyList(
|
|
from: data, format: nil) as? [String: String]
|
|
else { return [:] }
|
|
return plist
|
|
}
|
|
}
|