diff --git a/ylgamehall/Source/WebView/SubGameViewController.swift b/ylgamehall/Source/WebView/SubGameViewController.swift index cc1d34a..d30c19a 100644 --- a/ylgamehall/Source/WebView/SubGameViewController.swift +++ b/ylgamehall/Source/WebView/SubGameViewController.swift @@ -342,8 +342,13 @@ public final class SubGameViewController: UIViewController { bridge.call("phonestate", data: .string(state), callback: nil) } - // overlay(Phase 7)pop 回子游戏时反向 callback getWebdata。挂在子游戏栈顶 - // 期间生效;overlay push 上来时 viewWillDisappear → teardown 自动解绑,避免双发。 + // overlay(Phase 7)pop 回子游戏时反向 callback getWebdata。**lifecycle-tied**: + // 子游戏栈顶时挂钩,overlay push / 子游戏自身 pop 时由 viewWillDisappear 解绑。 + // 这一行为与大厅 observer 的"永久挂钩"配合,复现 msext 双观察者行为: + // - 子游戏自身 backgameData pop 回大厅:sub-game observer 在 viewWillDisappear + // 已解绑 → 仅大厅 observer 触发 → 大厅 H5 收到 getWebdata(msext 单发等价) + // - 子游戏内 overlay backgameData pop:sub-game.viewWillAppear 时 observer 重挂 + // → 大厅 + sub-game 双触发 → 大厅 H5 + sub-game H5 双发收到(msext 双观察者等价) subGameReturnObserver = NotificationCenter.default.addObserver( forName: .subGameDidReturn, object: nil, diff --git a/ylgamehall/Source/WebView/WebContainerViewController.swift b/ylgamehall/Source/WebView/WebContainerViewController.swift index 217893b..27556df 100644 --- a/ylgamehall/Source/WebView/WebContainerViewController.swift +++ b/ylgamehall/Source/WebView/WebContainerViewController.swift @@ -23,8 +23,12 @@ public final class WebContainerViewController: UIViewController { private let shakeHandler = ShakeHandler() // MARK: - .subGameDidReturn 观察者(子游戏 backgameData pop 后回大厅,反向 callback getWebdata) + // + // 与 msext NewRootVC.m:593 等价:永久挂钩,viewDidLoad 期间一次性 install, + // 不随 viewWillDisappear teardown。详见 setupExternalSubscriptions 注释。 private var subGameReturnObserver: NSObjectProtocol? + private var subGameReturnObserverInstalled = false // MARK: - Boot pipeline 内部错误分类 // @@ -447,18 +451,35 @@ public final class WebContainerViewController: UIViewController { bridge.call("appservice", data: .string("2"), callback: nil) } - // §3.2 [12] getWebdata — 子游戏 pop 回大厅,反向把 backgameData 入参透传给 H5 - // (msext NewRootVC 监听 "backgameDatatwo" 通知 → callHandler:"getWebdata" 等价) - // 大厅栈顶时挂钩,pop 进子游戏时由 viewWillDisappear teardown 解绑,避免双发。 - subGameReturnObserver = NotificationCenter.default.addObserver( - forName: .subGameDidReturn, - object: nil, - queue: .main - ) { note in - // queue=.main 保证 closure 在主线程;显式 hop 让 Swift 6 actor 推断通过 - let data = (note.userInfo?["data"] as? String) ?? "" - MainActor.assumeIsolated { - bridge.call("getWebdata", data: .string(data), callback: nil) + // §3.2 [12] getWebdata — 反向把 backgameData / 弹层 data 入参透传给大厅 H5。 + // + // 与 msext NewRootVC.m:593 等价:observer 在 viewDidLoad 期间一次性挂钩, + // **永久 listen,不随 viewWillDisappear teardown**。这样: + // + // 1. 子游戏 backgameData 自己 pop 回大厅: + // BackGameDataHandler → AppCoordinator.popSubGame → popViewController + // sub-game.viewWillDisappear → sub-game 的 observer 解绑(sub-game 走 lifecycle-tied) + // 再 post → 仅大厅 observer 触发 → 大厅 H5 收到 getWebdata(msext 单发等价) + // + // 2. 子游戏内弹层 backgameData pop(弹层 push 自 sub-game): + // OverlayViewController → AppCoordinator.popOverlay → popViewController + // overlay.viewWillDisappear → sub-game.viewWillAppear → sub-game observer 重新挂钩 + // 再 post → 大厅 + sub-game observer 同时触发 → 大厅 H5 + sub-game H5 双发收到 + // (msext NewRootVC + gameController 双观察者等价行为) + // + // 用 installed 标志保证多次 viewWillAppear 不重复挂钩(避免重复触发)。 + if !subGameReturnObserverInstalled { + subGameReturnObserverInstalled = true + subGameReturnObserver = NotificationCenter.default.addObserver( + forName: .subGameDidReturn, + object: nil, + queue: .main + ) { note in + // queue=.main 保证 closure 在主线程;显式 hop 让 Swift 6 actor 推断通过 + let data = (note.userInfo?["data"] as? String) ?? "" + MainActor.assumeIsolated { + bridge.call("getWebdata", data: .string(data), callback: nil) + } } } } @@ -469,15 +490,13 @@ public final class WebContainerViewController: UIViewController { // 单例 monitor 全局只有一个 closure 引用,子游戏 setup 时会覆盖; // 大厅 viewWillDisappear 时 teardown 释放对当前 bridge/webView 的引用 // 避免双发。 + // + // subGameReturnObserver 是永久挂钩(与 msext NewRootVC observer 等价), + // **不在此 teardown** —— 详见 setupExternalSubscriptions 注释。 BatteryMonitor.shared.onChange = nil NetworkMonitor.shared.onChange = nil AppLifecycleObserver.shared.onBackground = nil AppLifecycleObserver.shared.onForeground = nil - - if let token = subGameReturnObserver { - NotificationCenter.default.removeObserver(token) - subGameReturnObserver = nil - } } // MARK: - Alerts (terminal / retry states)