Files
youle_app_ios_v2/ylgamehall/Source/Resource/LocalVersionReader.swift
T
joywayer 9e50c38323 Phase 1.12:LocalVersionReader 读 version.xml + ATS 全局放行
新增 LocalVersionReader 读两个本地版本号 + Info.plist 加
NSAllowsArbitraryLoads 全局放行 HTTP(为 Phase 1.13 zip 下载链路准备)。

- 新增 ylgamehall/Source/Resource/LocalVersionReader.swift
  - public enum LocalVersionReader,nonisolated static 命名空间
  - localAppVersion: Int 从 BundleConfig.shared.appVersion 转 Int
    (沿用 ChannelConfig.plist 注入),解析失败返回 0
  - localGameVersion: Int 用 Foundation XMLParser SAX 风格解析
    SandboxPaths.lobbyVersionXML 的 /game/version@value 节点,
    缺失 / 损坏返回 0(msext NewRootVC.m:664-682 `[nil intValue]=0`
    兜底语义,是首装后首次升级的关键机制)
  - subGameVersion(dir:start:) 子游戏版本号读取,Phase 6 用
  - 内部 VersionXMLHandler 标 nonisolated(Swift 6 严格并发下
    XMLParserDelegate 跨边界),SAX 风格只在 didStartElement 抓
    version 节点的 value 属性
- Info.plist 加 NSAppTransportSecurity / NSAllowsArbitraryLoads = true
  - 企业签 / 超签分发场景,后端域名易变(zip CDN / 配置服 /
    七牛 / 错误日志上报等),白名单维护成本高于安全收益
  - 全局放行的副作用主要影响第三方 SDK 流量;本项目无意外引入未审计
    SDK,且第三方 SDK 自身一般也走 HTTPS,不受影响
  - 详见 ADR-008 决策上下文与 CLAUDE.md「不上架 App Store,企业签 /
    超签 / TF 分发」契约
- RootViewController 烟雾测试串接 LocalVersionReader:打印本地 vs 远端
  对比 + IPA / zip 升级判定
- 真机实测(现网 .txt 本地 + 当前 IPA 内 gamehall.zip 解压物):
  - 本地 appVersion = 43, 远端 = 43 → 不升级
  - 本地 gameVersion = 260(IPA 内 2023-12 zip 的 version.xml),
    远端 = 261 → 需要 zip 升级。Phase 1.13 将实现下载 +
    解压 + 替换链路
2026-06-22 02:24:22 +08:00

76 lines
3.0 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.
//
// LocalVersionReader.swift
// ylgamehall
//
// 读本地两个版本号:
// - 原生 IPA 版本(来自 ChannelConfig.plist 的 appversion 字段)
// - H5 包版本(来自 Library/Caches/{gamedir}/{gamestart}/version.xml 的 /game/version@value
//
// 详见 docs/Development-Plan.md ADR-008-F / msext NewRootVC.m:664-682。
//
import Foundation
public enum LocalVersionReader {
/// 原生 IPA 版本。等价 msext `[FuncPublic filename:@"appversion"] integerValue`
/// 来源:ChannelConfig.plist 的 `appversion` 字段(沿用渠道注入约定,
/// IPA 后处理改 plist 可动态调整)。
/// 解析失败返回 0msext `[nil intValue] = 0` 兜底语义)。
nonisolated public static var localAppVersion: Int {
Int(BundleConfig.shared.appVersion) ?? 0
}
/// 大厅 H5 包版本。等价 msext `NewRootVC.m:664-682` 的 GDataXML 路径,
/// 新外壳用 Foundation `XMLParser`(无外部依赖)解析。
///
/// 文件路径:`SandboxPaths.lobbyVersionXML` =
/// `Library/Caches/{gamedir}/{gamestart}/version.xml`
/// 解析 `/game/version` 节点的 `value` 属性,转 Int。
///
/// **缺失 / 损坏返回 0**(必须保留,首装后首次升级的兜底机制:
/// `[nil intValue] = 0`,任何远端 gameVersion > 0 触发下载)。
nonisolated public static var localGameVersion: Int {
readGameVersion(from: SandboxPaths.lobbyVersionXML) ?? 0
}
/// 子游戏 H5 包版本。等价 msext `gameController.m:1249-1254`。
nonisolated public static func subGameVersion(dir: String, start: String) -> Int {
let url = SandboxPaths.caches
.appendingPathComponent(dir)
.appendingPathComponent(start)
.appendingPathComponent("version.xml")
return readGameVersion(from: url) ?? 0
}
// MARK: - 内部 XML 解析
nonisolated private static func readGameVersion(from url: URL) -> Int? {
guard let data = try? Data(contentsOf: url) else { return nil }
let parser = XMLParser(data: data)
let handler = VersionXMLHandler()
parser.delegate = handler
guard parser.parse(), let v = handler.gameVersion else { return nil }
return v
}
}
// MARK: - XMLParser delegate
/// 仅提取 `<version value="N"/>`(在 `/game/version` 路径下)的 value 转 Int。
/// msext 的 xpath 是 `/game/version` 取属性 `value`;新外壳用 SAX 风格 XMLParser
/// 也只需要在 `version` 元素出现时取属性即可(嵌套层级宽容化处理)。
nonisolated private final class VersionXMLHandler: NSObject, XMLParserDelegate {
var gameVersion: Int?
func parser(_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String: String] = [:]) {
if elementName == "version", let raw = attributeDict["value"], let v = Int(raw) {
gameVersion = v
}
}
}