Phase 2.C:ExternalSubscriptions 生命周期管理(防双发预备)

按 Design §2.4.2 把 battery / network / appservice 三组外部事件源的订阅
挂钩与 VC 生命周期配对:viewWillAppear → setup,viewWillDisappear →
teardown。配套约束:未来 Phase 6 子游戏 push 上来时大厅自动 teardown,
避免桥事件双发(栈深 ≤ 2,§2.4.3)。

WebContainerViewController 改造:
  - 新增 setupExternalSubscriptions():
    * 幂等 start 3 个 monitor(NetworkMonitor / BatteryMonitor /
      AppLifecycleObserver)
    * 设 BatteryMonitor.onChange → evaluateJavaScript("window.app_getbattery=N")
      + bridge.call("getBattery", "%.2f")
    * 设 NetworkMonitor.onChange → evaluateJavaScript("window.app_getnetwork=N")
      + bridge.call("getnetwork", "1"/"2"/"3")
    * 设 AppLifecycleObserver.onBackground/Foreground → bridge.call("appservice",
      "1"/"2")
  - 新增 teardownExternalSubscriptions():
    * 解绑 onChange / on{Background,Foreground} = nil
    * 释放对 bridge/webView 的 closure 引用避免子游戏 push 后双发
    * monitor 不停(保持后台 currentXxx 状态新鲜),下次 setup 直接 resume
  - viewWillAppear / viewWillDisappear override 调 setup / teardown 配对
  - writeAppDataFiles 精简:只写首次值 + 启动 NetworkMonitor/BatteryMonitor
    (需要 currentXxx 读首次值);AppLifecycleObserver 启动挪到 setup

至此 Phase 2 全部完成(2.A 8 个 handler + 2.B 5 项反向 callback + 2.C
生命周期管理)。

BuildProject 通过

Plan §5 Phase 2.13 勾选

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-22 19:02:35 +08:00
co-authored by Claude Opus 4.7
parent 4986f36967
commit 09f071e38f
2 changed files with 55 additions and 20 deletions
@@ -75,11 +75,22 @@ public final class WebContainerViewController: UIViewController {
public override var canBecomeFirstResponder: Bool { true }
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// push §2.4.2
setupExternalSubscriptions()
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
teardownExternalSubscriptions()
}
public override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
shakeHandler.handleMotionEnded(motion)
}
@@ -216,10 +227,10 @@ public final class WebContainerViewController: UIViewController {
)
}
/// 4 app_*.js battery / network / appservice
/// 1 .js reload
/// 2`bridge.call` H5§3.2 234
/// msext NewRootVC.initJSdata + changebattery + changenetstate + appservice
/// 4 app_*.js loadFileURL monitor
/// `setupExternalSubscriptions` / `teardownExternalSubscriptions`
/// Design §2.4.2 ExternalSubscriptions push
/// VC H5
private func writeAppDataFiles(resolved: ResolvedVersion) throws {
let writer = AppDataWriter(
bundleConfig: .shared,
@@ -227,27 +238,40 @@ public final class WebContainerViewController: UIViewController {
containerRole: .lobby
)
// 3 monitor started
// 2 currentXxx monitor便
// AppLifecycleObserver currentXxx setup
NetworkMonitor.shared.start()
BatteryMonitor.shared.start()
AppLifecycleObserver.shared.start()
// 4
try writer.writeInitial()
try writer.writeBattery(BatteryMonitor.shared.currentLevel)
try writer.writeNetwork(NetworkMonitor.shared.currentCode)
}
// MARK: - Phase 2.C / Design §2.4.2
//
// battery / network / appservice VC
// viewWillAppear setupviewWillDisappear teardown
// Phase 6 push viewWillDisappear
// teardown §2.4.3 2
//
// **** evaluateJavaScript window.app_*
// H5 <script src> fetch H5
// app_xxx §3.2 callback handler
// H5 docs/H5-Native-Implementation-Design.md §7.5.2 / §7.5.4
private func setupExternalSubscriptions() {
// 3 monitorwriteAppDataFiles NetworkMonitor /
// BatteryMonitorAppLifecycleObserver setup
NetworkMonitor.shared.start()
BatteryMonitor.shared.start()
AppLifecycleObserver.shared.start()
let bridge = bridgedWebView.bridge
let webView = bridgedWebView.webView
// **** evaluateJavaScript
// window.app_* H5 <script src> fetch
// H5 app_xxx
// §3.2 callback handler H5
// docs/H5-Native-Implementation-Design.md §7.5.2 / §7.5.4
// §3.2 2 getBattery UIDevice
// data: "%.2f"
BatteryMonitor.shared.onChange = { level in
let value = String(format: "%.2f", level)
Task { @MainActor in
@@ -256,8 +280,7 @@ public final class WebContainerViewController: UIViewController {
bridge.call("getBattery", data: .string(value), callback: nil)
}
// §3.2 3 getnetwork NWPath
// data: "1"= / "2"=WiFi / "3"=
// §3.2 3 getnetwork NWPath 1 / 2 WiFi / 3
NetworkMonitor.shared.onChange = { code in
Task { @MainActor in
_ = try? await webView.evaluateJavaScript("window.app_getnetwork=\(code);")
@@ -266,8 +289,6 @@ public final class WebContainerViewController: UIViewController {
}
// §3.2 4 appservice App 沿
// data: "1"= / "2"=
// appservice app_* bridge.call
AppLifecycleObserver.shared.onBackground = {
bridge.call("appservice", data: .string("1"), callback: nil)
}
@@ -276,6 +297,18 @@ public final class WebContainerViewController: UIViewController {
}
}
private func teardownExternalSubscriptions() {
// onChange / on{Background,Foreground} monitor
// currentXxx setup resume
// monitor closure setup
// viewWillDisappear teardown bridge/webView
//
BatteryMonitor.shared.onChange = nil
NetworkMonitor.shared.onChange = nil
AppLifecycleObserver.shared.onBackground = nil
AppLifecycleObserver.shared.onForeground = nil
}
// MARK: - Alerts (terminal / retry states)
private var appDisplayName: String {