Phase 1.6:BridgeProtocol + BridgeData 协议骨架

桥核心的纯类型层,不依赖 WKWebView,便于 Phase 1.7 BridgeBus 实现与
单测注入 mock 都基于此抽象。

- 新增 ylgamehall/Source/Bridge/BridgeProtocol.swift
  - protocol BridgeProtocol: AnyObject, Sendable
    - register(_:handler:) 注册 H5 → Native handler,重复名覆盖
    - call(_:data:callback:) Native → H5 主动调用,callback 可选
  - typealias BridgeHandler = @Sendable (BridgeData?, BridgeCallback?)
    async -> Void,允许 handler 内部 await
  - typealias BridgeCallback = @Sendable (BridgeData?) -> Void
  - enum BridgeData: Sendable 六态(string/number/bool/null/array/object),
    与 JSON 值同构
- BridgeData JSON 互转:
  - init?(jsonObject:) 从 JSONSerialization 输出递归构造;NSNumber
    Bool/Double 鉴别用 CFGetTypeID 避免 NSNumber(value:true) 被当 1.0
  - var jsonObject: Any 反向,供 evaluateJavaScript 拼 JSON 字符串
- BridgeData 访问语法糖:asString/asInt/asDouble/asBool/asArray/asObject
  + subscript(key:) 对象字段访问 + subscript(index:) 数组访问
- BridgeData 字面量构造:ExpressibleByStringLiteral/IntegerLiteral/
  FloatLiteral/BooleanLiteral/ArrayLiteral/DictionaryLiteral,
  fixture / handler 代码 ["k": 1, "v": true] 直接构造 .object
- BuildProject 通过,Sendable 全自动派生
This commit is contained in:
joywayer
2026-06-22 00:05:26 +08:00
parent 463f0fc6fe
commit f740274d59
@@ -0,0 +1,173 @@
//
// BridgeProtocol.swift
// ylgamehall
//
// H5 Native docs/H5-Native-Implementation-Design.md §3.2
//
import Foundation
// MARK: -
/// WebView VC handler 便 mock
///
/// - `BridgeBus`@MainActor WKWebView
/// - H5 `Resources/JS/WebViewJavascriptBridge.js`Phase 1.8
public protocol BridgeProtocol: AnyObject, Sendable {
/// H5 Native handler name
func register(_ name: String, handler: @escaping BridgeHandler)
/// Native H5 H5 handler`callback`
func call(_ name: String, data: BridgeData?, callback: BridgeCallback?)
}
/// H5 Native handler
///
/// - 1 H5 `bridge.callHandler('name', data, ...)` data
/// - 2 handler `callback?(...)` H5
/// - async handler await SDK /
public typealias BridgeHandler = @Sendable (BridgeData?, BridgeCallback?) async -> Void
/// H5 Native responseCallback Native H5
public typealias BridgeCallback = @Sendable (BridgeData?) -> Void
// MARK: - BridgeData
/// JSON
///
/// H5 Native 便
/// - 访`data["title"]?.asString` `data["title"] as? String`
/// - Sendable actor `[String: Any]` non-Sendable
/// - fixture `["a": 1, "b": [true, nil]]` `.object([...])`
public enum BridgeData: Sendable {
case string(String)
case number(Double)
case bool(Bool)
case null
case array([BridgeData])
case object([String: BridgeData])
}
// MARK: - JSON
extension BridgeData {
/// `JSONSerialization` Any
/// nil便 payload
public init?(jsonObject: Any) {
if jsonObject is NSNull { self = .null; return }
if let s = jsonObject as? String { self = .string(s); return }
if let n = jsonObject as? NSNumber {
// NSNumber Bool/Int/DoubleBool CFTypeID
// NSNumber(value: true) 1.0
if CFGetTypeID(n) == CFBooleanGetTypeID() {
self = .bool(n.boolValue)
return
}
self = .number(n.doubleValue)
return
}
if let arr = jsonObject as? [Any] {
self = .array(arr.compactMap { BridgeData(jsonObject: $0) })
return
}
if let obj = jsonObject as? [String: Any] {
var out: [String: BridgeData] = [:]
out.reserveCapacity(obj.count)
for (k, v) in obj {
if let d = BridgeData(jsonObject: v) { out[k] = d }
}
self = .object(out)
return
}
return nil
}
/// `JSONSerialization` Any evaluateJavaScript JSON
public var jsonObject: Any {
switch self {
case .string(let s): return s
case .number(let d): return d
case .bool(let b): return b
case .null: return NSNull()
case .array(let arr): return arr.map { $0.jsonObject }
case .object(let d): return d.mapValues { $0.jsonObject }
}
}
}
// MARK: - 访
extension BridgeData {
public var asString: String? {
if case .string(let s) = self { return s }
return nil
}
public var asDouble: Double? {
if case .number(let d) = self { return d }
return nil
}
public var asInt: Int? {
if case .number(let d) = self { return Int(d) }
return nil
}
public var asBool: Bool? {
if case .bool(let b) = self { return b }
return nil
}
public var asArray: [BridgeData]? {
if case .array(let a) = self { return a }
return nil
}
public var asObject: [String: BridgeData]? {
if case .object(let o) = self { return o }
return nil
}
/// 访`data["title"]?.asString`
public subscript(key: String) -> BridgeData? {
if case .object(let o) = self { return o[key] }
return nil
}
/// 访`data[0]?.asString`
public subscript(index: Int) -> BridgeData? {
if case .array(let a) = self, a.indices.contains(index) { return a[index] }
return nil
}
}
// MARK: -
extension BridgeData: ExpressibleByStringLiteral {
public init(stringLiteral value: String) { self = .string(value) }
}
extension BridgeData: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) { self = .number(Double(value)) }
}
extension BridgeData: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) { self = .number(value) }
}
extension BridgeData: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) { self = .bool(value) }
}
extension BridgeData: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: BridgeData...) { self = .array(elements) }
}
extension BridgeData: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, BridgeData)...) {
self = .object(Dictionary(uniqueKeysWithValues: elements))
}
}