Phase 2.A:大厅 8 个简单 handler 落地 + BridgeData 加 nonisolated

闭合 Phase 1.17 联调最后一公里(解决 Xcode 日志 4 个 "no handler registered"
+ 提前落地 4 个无外部依赖的简单 handler)。

新增 7 个 handler 模块(Source/Bridge/Handlers/):
  - ClipboardHandler.swift           §3.1 [13][14]剪贴板 UIPasteboard.general
  - ShakeHandler.swift               §3.1 [7][8][9]摇一摇 + §3.2 [5]shakeEnd 反向
                                       canShake / canVoice 状态 + motionEnded 转发钩子
  - VoicePlayingHandler.swift        §3.1 [6]actor VoiceCenter.shared 开关位
  - DeviceInfoHandler.swift          §3.1 [21]+ §3.2 [1]getphoneInfo 大写 I 入、
                                       getphoneinfo 小写 i 出反向 callback
                                       DeviceInfoSnapshot 6 字段(IDFA 用 idfv 兜底,
                                       CLAUDE.md 不接 ATT/IDFA)
  - BrowserHandler.swift             §3.1 [16]UIApplication.open async(Task @MainActor
                                       异步打开,闭包先 cb 不等 open 完成)
  - OpenSaomaHandler.swift           §3.1 [22]空 stub(契约要求注册)
  - StartLocationHandler.swift       §3.1 [20]Phase 5 完整实现,Phase 2 stub

扩展 VibratorHandler.swift:
  - §3.1 [10]vibrator(原有)+ [11]repeatvibrator(msext RootVC.m:1820 沿用:
    实际单次 vibrate 不真循环)+ [12]canclevibrator

BridgeProtocol.swift BridgeData 加 nonisolated:
  - 项目默认 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor 让 BridgeData 跨 actor
    访问被拦;asString / asDouble / asInt / asBool / asArray / asObject /
    subscript 全部标 nonisolated(纯值类型本就该是 isolation-free)
  - 影响所有 @Sendable async handler 闭包内的 data?.asXxx 访问

WebContainerViewController 接入:
  - 加 private let shakeHandler = ShakeHandler() 持有
  - registerBridgeHandlers() 注册 8 个 handler 模块
  - 加 canBecomeFirstResponder = true + viewDidAppear becomeFirstResponder
  - 加 motionEnded(_:with:) 转发到 shakeHandler.handleMotionEnded
    (motion 检测必需 first responder,否则不触发)

BuildProject 通过。重装 app 后 H5 启动应不再报 "no handler registered" 错误。

Plan §5 Phase 2.A 全部勾选(含 startlocation bonus stub + BridgeData
nonisolated 重构)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-22 18:38:00 +08:00
co-authored by Claude Opus 4.7
parent 0a313afe8e
commit d38ff0f8c4
11 changed files with 364 additions and 33 deletions
@@ -0,0 +1,69 @@
//
// DeviceInfoHandler.swift
// ylgamehall
//
// H5 Native handlergetphoneInfo + callback getphoneinfo i
// docs/H5-Native-Contract.md §3.1 21 + §3.2 1 + A
//
//
// H5 bridge.callHandler("getphoneInfo", ...) I
// bridge.callHandler("getphoneinfo", ...) i
//
import UIKit
@MainActor
public enum DeviceInfoHandler {
public static func register(on bridge: any BridgeProtocol) {
// 21getphoneInfo I
// cb"getphoneInfo"
// bridge.call("getphoneinfo" idata: A 6 )
bridge.register("getphoneInfo") { [weak bridge] _, callback in
callback?(.string("getphoneInfo"))
Task { @MainActor in
let snapshot = DeviceInfoSnapshot.current()
bridge?.call("getphoneinfo", // i
data: .object(snapshot.bridgeFields),
callback: nil)
}
}
}
}
/// §3.2 A 6 string
@MainActor
public struct DeviceInfoSnapshot {
public let phoneAdresseMAC: String // UIDevice.identifierForVendor.uuidString
public let phoneDeviceBrand: String // "iPhone" / "iPad"
public let phoneIMEI: String // IDFA idfvIDFA ATT
public let phoneModel: String // UIDevice.localizedModel
public let phoneProvidersName: String // iOS 16+ /
public let phoneVersion: String // UIDevice.systemVersion
public static func current() -> DeviceInfoSnapshot {
let device = UIDevice.current
let idfv = device.identifierForVendor?.uuidString ?? ""
return DeviceInfoSnapshot(
phoneAdresseMAC: idfv,
phoneDeviceBrand: device.model,
phoneIMEI: idfv, // CLAUDE.md ATT/IDFA沿 idfv
phoneModel: device.localizedModel,
phoneProvidersName: "", // iOS 16+ CTCarrier
phoneVersion: device.systemVersion
)
}
/// §3.2 A
public var bridgeFields: [String: BridgeData] {
[
"PhoneAdresseMAC": .string(phoneAdresseMAC),
"PhoneDeviceBrand": .string(phoneDeviceBrand),
"PhoneIMEI": .string(phoneIMEI),
"PhoneModel": .string(phoneModel),
"PhoneProvidersName": .string(phoneProvidersName),
"PhoneVersion": .string(phoneVersion)
]
}
}