1. 高级JavaScript桥接架构
1.1 完整的双向通信系统
import WebKit
import UIKit
// MARK: - 消息协议定义
protocol JavaScriptMessageProtocol: Codable {
var id: String { get set }
var method: String { get }
var params: [String: Any] { get }
var timestamp: Date { get }
}
struct RequestMessage: JavaScriptMessageProtocol {
var id: String
let method: String
let params: [String: Any]
let timestamp: Date
init(id: String = UUID().uuidString, method: String, params: [String: Any] = [:]) {
self.id = id
self.method = method
self.params = params
self.timestamp = Date()
}
}
struct ResponseMessage: Codable {
let id: String
let success: Bool
let result: Any?
let error: String?
let timestamp: Date
}
// MARK: - 高级桥接管理器
class AdvancedJavaScriptBridge: NSObject {
// MARK: - Properties
weak var webView: WKWebView?
private let userContentController: WKUserContentController
private var messageHandlers: [String: MessageHandler] = [:]
private var pendingRequests: [String: (ResponseMessage) -> Void] = [:]
private let queue = DispatchQueue(label: "jsbridge.queue", qos: .userInitiated)
// MARK: - 消息处理器协议
protocol MessageHandler {
func handleMessage(_ message: RequestMessage,
completion: @escaping (ResponseMessage) -> Void)
}
// MARK: - 初始化
init(webView: WKWebView) {
let config = webView.configuration
self.userContentController = config.userContentController
self.webView = webView
super.init()
setupDefaultHandlers()
injectJavaScriptBridge()
}
// MARK: - 默认处理器设置
private func setupDefaultHandlers() {
// 通用处理器
registerHandler("log", handler: LogHandler())
registerHandler("storage", handler: StorageHandler())
registerHandler("device", handler: DeviceHandler())
registerHandler("network", handler: NetworkHandler())
registerHandler("camera", handler: CameraHandler())
registerHandler("location", handler: LocationHandler())
}
// MARK: - JavaScript桥接注入
private func injectJavaScriptBridge() {
let bridgeScript = """
(function() {
// 高级JavaScript桥接实现
window.JSBridge = {
_messageId: 0,
_callbacks: {},
_queue: [],
// 发送消息到原生
callNative: function(method, params, callback) {
var messageId = 'msg_' + (++this._messageId);
var message = {
id: messageId,
method: method,
params: params || {},
timestamp: Date.now()
};
if (callback) {
this._callbacks[messageId] = callback;
}
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {
window.webkit.messageHandlers.bridge.postMessage(message);
} else {
this._queue.push(message);
}
return messageId;
},
// 处理原生响应
handleNativeResponse: function(response) {
var callback = this._callbacks[response.id];
if (callback) {
callback(response.success, response.result, response.error);
delete this._callbacks[response.id];
}
},
// 批量调用
batchCall: function(calls, callback) {
var results = [];
var completed = 0;
var total = calls.length;
var batchCallback = function(success, result, error) {
results.push({success: success, result: result, error: error});
completed++;
if (completed === total) {
if (callback) {
callback(results);
}
}
};
calls.forEach(function(call) {
this.callNative(call.method, call.params, batchCallback);
});
},
// 订阅/发布模式
subscribe: function(event, callback) {
if (!window.JSBridge._subscriptions) {
window.JSBridge._subscriptions = {};
}
if (!window.JSBridge._subscriptions[event]) {
window.JSBridge._subscriptions[event] = [];
}
window.JSBridge._subscriptions[event].push(callback);
},
publish: function(event, data) {
if (window.JSBridge._subscriptions && window.JSBridge._subscriptions[event]) {
window.JSBridge._subscriptions[event].forEach(function(callback) {
callback(data);
});
}
},
// 工具方法
ready: function(callback) {
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {
callback();
} else {
var checkReady = setInterval(function() {
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {
clearInterval(checkReady);
callback();
}
}, 100);
}
}
};
// 全局错误处理
window.addEventListener('error', function(e) {
JSBridge.callNative('error', {
type: 'js_error',
message: e.message,
filename: e.filename,
lineno: e.lineno
});
});
// 性能监控
if (performance && performance.mark) {
JSBridge.callNative('performance', {
type: 'page_load_start',
navigationStart: performance.timing.navigationStart
});
}
})();
"""
let userScript = WKUserScript(
source: bridgeScript,
injectionTime: .atDocumentStart,
forMainFrameOnly: false
)
userContentController.addUserScript(userScript)
// 注册桥接处理器
userContentController.add(self, name: "bridge")
}
// MARK: - 注册自定义处理器
func registerHandler(_ name: String, handler: MessageHandler) {
queue.async { [weak self] in
self?.messageHandlers[name] = handler
}
// 通知JavaScript新的处理器可用
let jsCode = "JSBridge.publish('handler_added', {name: '\(name)'});"
webView?.evaluateJavaScript(jsCode)
}
// MARK: - 发送响应到JavaScript
func sendResponse(_ response: ResponseMessage) {
let jsonData = try? JSONSerialization.data(withJSONObject: response, options: [])
let jsonString = String(data: jsonData ?? Data(), encoding: .utf8) ?? "{}"
let jsCode = """
if (typeof JSBridge !== 'undefined' && JSBridge.handleNativeResponse) {
JSBridge.handleNativeResponse(\(jsonString));
}
"""
webView?.evaluateJavaScript(jsCode) { _, error in
if let error = error {
print("Failed to send response: \(error)")
}
}
}
// MARK: - 发布事件
func publishEvent(_ event: String, data: [String: Any]) {
let payload: [String: Any] = [
"event": event,
"data": data,
"timestamp": ISO8601DateFormatter().string(from: Date())
]
let jsonData = try? JSONSerialization.data(withJSONObject: payload, options: [])
let jsonString = String(data: jsonData ?? Data(), encoding: .utf8) ?? "{}"
let jsCode = "if (typeof JSBridge !== 'undefined') { JSBridge.publish('\(event)', \(jsonString)); }"
webView?.evaluateJavaScript(jsCode)
}
}
// MARK: - WKScriptMessageHandler
extension AdvancedJavaScriptBridge: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any],
let id = body["id"] as? String,
let method = body["method"] as? String else {
return
}
let params = body["params"] as? [String: Any] ?? [:]
let request = RequestMessage(id: id, method: method, params: params)
queue.async { [weak self] in
self?.handleRequest(request)
}
}
private func handleRequest(_ request: RequestMessage) {
let handler = messageHandlers[request.method]
if let handler = handler {
handler.handleMessage(request) { [weak self] response in
self?.sendResponse(response)
}
} else {
let errorResponse = ResponseMessage(
id: request.id,
success: false,
result: nil,
error: "Method '\(request.method)' not found",
timestamp: Date()
)
sendResponse(errorResponse)
}
}
}
2. 高级消息处理器实现
2.1 日志处理器
// MARK: - 日志处理器
class LogHandler: AdvancedJavaScriptBridge.MessageHandler {
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.params["level"] as? String {
case "debug":
print("🔍 [JS DEBUG] \(message.params["message"] ?? "Unknown")")
case "info":
print("ℹ️ [JS INFO] \(message.params["message"] ?? "Unknown")")
case "warn":
print("⚠️ [JS WARN] \(message.params["message"] ?? "Unknown")")
case "error":
print("❌ [JS ERROR] \(message.params["message"] ?? "Unknown")")
default:
print("📝 [JS LOG] \(message.params["message"] ?? "Unknown")")
}
let response = ResponseMessage(
id: message.id,
success: true,
result: ["timestamp": ISO8601DateFormatter().string(from: Date())],
error: nil,
timestamp: Date()
)
completion(response)
}
}
2.2 存储处理器
// MARK: - 存储处理器
class StorageHandler: AdvancedJavaScriptBridge.MessageHandler {
private let defaults = UserDefaults.standard
private let storageKey = "js_storage"
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "setItem":
setItem(message)
case "getItem":
getItem(message, completion)
case "removeItem":
removeItem(message)
case "clear":
clear(message)
default:
let error = "Unknown storage method: \(message.method)"
completion(createErrorResponse(message.id, error: error))
}
}
private func setItem(_ message: RequestMessage) {
guard let key = message.params["key"] as? String,
let value = message.params["value"] else {
completion(createErrorResponse(message.id, error: "Missing key or value"))
return
}
var storage = getStorage()
storage[key] = value
saveStorage(storage)
completion(createSuccessResponse(message.id, result: ["success": true]))
}
private func getItem(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let key = message.params["key"] as? String else {
completion(createErrorResponse(message.id, error: "Missing key"))
return
}
let storage = getStorage()
let value = storage[key]
completion(createSuccessResponse(message.id, result: ["value": value ?? NSNull()]))
}
private func removeItem(_ message: RequestMessage) {
guard let key = message.params["key"] as? String else {
completion(createErrorResponse(message.id, error: "Missing key"))
return
}
var storage = getStorage()
storage.removeValue(forKey: key)
saveStorage(storage)
completion(createSuccessResponse(message.id, result: ["success": true]))
}
private func clear(_ message: RequestMessage) {
defaults.removeObject(forKey: storageKey)
completion(createSuccessResponse(message.id, result: ["success": true]))
}
private func getStorage() -> [String: Any] {
return defaults.dictionary(forKey: storageKey) ?? [:]
}
private func saveStorage(_ storage: [String: Any]) {
defaults.set(storage, forKey: storageKey)
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
2.3 设备信息处理器
// MARK: - 设备信息处理器
class DeviceHandler: AdvancedJavaScriptBridge.MessageHandler {
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "getInfo":
getDeviceInfo(message, completion)
case "vibrate":
vibrate(message, completion)
case "getBattery":
getBatteryInfo(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown device method"))
}
}
private func getDeviceInfo(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let deviceInfo: [String: Any] = [
"platform": "iOS",
"version": UIDevice.current.systemVersion,
"model": UIDevice.current.model,
"name": UIDevice.current.name,
"systemName": UIDevice.current.systemName,
"isSimulator": TARGET_OS_SIMULATOR != 0,
"language": Locale.current.languageCode ?? "en",
"region": Locale.current.regionCode ?? "US",
"screen": [
"width": UIScreen.main.bounds.width,
"height": UIScreen.main.bounds.height,
"scale": UIScreen.main.scale
],
"app": [
"version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0",
"build": Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1",
"name": Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "App"
]
]
completion(createSuccessResponse(message.id, result: deviceInfo))
}
private func vibrate(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let duration = message.params["duration"] as? TimeInterval ?? 0.1
DispatchQueue.main.async {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
completion(self.createSuccessResponse(message.id, result: ["success": true]))
}
}
private func getBatteryInfo(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
UIDevice.current.isBatteryMonitoringEnabled = true
let batteryInfo: [String: Any] = [
"level": UIDevice.current.batteryLevel * 100,
"state": batteryStateToString(UIDevice.current.batteryState),
"isCharging": UIDevice.current.batteryState != .unplugged
]
completion(createSuccessResponse(message.id, result: batteryInfo))
}
private func batteryStateToString(_ state: UIDevice.BatteryState) -> String {
switch state {
case .unknown: return "unknown"
case .unplugged: return "unplugged"
case .charging: return "charging"
case .full: return "full"
@unknown default: return "unknown"
}
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
2.4 网络处理器
// MARK: - 网络处理器
import Network
class NetworkHandler: AdvancedJavaScriptBridge.MessageHandler, ObservableObject {
private var monitor: NWPathMonitor?
private var isConnected = true
@Published var connectionType: String = "unknown"
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "getStatus":
getNetworkStatus(message, completion)
case "upload":
uploadFile(message, completion)
case "download":
downloadFile(message, completion)
case "fetch":
makeHTTPRequest(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown network method"))
}
}
private func getNetworkStatus(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let status: [String: Any] = [
"connected": isConnected,
"type": connectionType,
"effectiveType": effectiveType(),
"downlink": downlinkSpeed()
]
completion(createSuccessResponse(message.id, result: status))
}
private func makeHTTPRequest(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let urlString = message.params["url"] as? String,
let url = URL(string: urlString) else {
completion(createErrorResponse(message.id, error: "Invalid URL"))
return
}
var request = URLRequest(url: url)
request.httpMethod = message.params["method"] as? String ?? "GET"
// 添加headers
if let headers = message.params["headers"] as? [String: String] {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
// 添加body
if let body = message.params["body"] {
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
completion(self.createErrorResponse(message.id, error: error.localizedDescription))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(self.createErrorResponse(message.id, error: "Invalid response"))
return
}
var result: [String: Any] = [
"status": httpResponse.statusCode,
"headers": httpResponse.allHeaderFields
]
if let data = data, let json = try? JSONSerialization.jsonObject(with: data) {
result["body"] = json
} else if let data = data, let text = String(data: data, encoding: .utf8) {
result["body"] = text
}
completion(self.createSuccessResponse(message.id, result: result))
}
}.resume()
}
private func uploadFile(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
// 文件上传实现
guard let urlString = message.params["url"] as? String,
let url = URL(string: urlString),
let filePath = message.params["filePath"] as? String,
let fileURL = URL(string: filePath) else {
completion(createErrorResponse(message.id, error: "Invalid parameters"))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.uploadTask(with: request, fromFile: fileURL) { data, response, error in
DispatchQueue.main.async {
if let error = error {
completion(self.createErrorResponse(message.id, error: error.localizedDescription))
} else {
let result: [String: Any] = [
"success": true,
"response": response?.description ?? ""
]
completion(self.createSuccessResponse(message.id, result: result))
}
}
}
task.resume()
}
private func downloadFile(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
// 文件下载实现
guard let urlString = message.params["url"] as? String,
let url = URL(string: urlString) else {
completion(createErrorResponse(message.id, error: "Invalid URL"))
return
}
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destination = documentsPath.appendingPathComponent(url.lastPathComponent)
let task = URLSession.shared.downloadTask(with: url) { tempURL, response, error in
if let error = error {
DispatchQueue.main.async {
completion(self.createErrorResponse(message.id, error: error.localizedDescription))
}
} else if let tempURL = tempURL {
do {
try FileManager.default.moveItem(at: tempURL, to: destination)
let result: [String: Any] = [
"success": true,
"filePath": destination.path,
"fileSize": try FileManager.default.attributesOfItem(atPath: destination.path)["NSFileSize"] as? Int ?? 0
]
DispatchQueue.main.async {
completion(self.createSuccessResponse(message.id, result: result))
}
} catch {
DispatchQueue.main.async {
completion(self.createErrorResponse(message.id, error: error.localizedDescription))
}
}
}
}
task.resume()
}
private func effectiveType() -> String {
// 简化的连接类型判断
return connectionType == "wifi" ? "4g" : "2g"
}
private func downlinkSpeed() -> Double {
// 简化的下载速度估算
return connectionType == "wifi" ? 10.0 : 1.0 // MB/s
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
// MARK: - 网络状态监控
func startMonitoring() {
monitor = NWPathMonitor()
monitor?.pathUpdateHandler = { [weak self] path in
self?.isConnected = path.status == .satisfied
self?.connectionType = self?.pathToString(path) ?? "unknown"
// 通知JavaScript网络状态变化
let status: [String: Any] = [
"connected": self?.isConnected ?? false,
"type": self?.connectionType ?? "unknown"
]
self?.bridge?.publishEvent("networkStatusChanged", data: status)
}
monitor?.start(queue: DispatchQueue.global(qos: .background))
}
private func pathToString(_ path: NWPath) -> String {
if path.usesInterfaceType(.wifi) { return "wifi" }
if path.usesInterfaceType(.cellular) { return "cellular" }
if path.usesInterfaceType(.wired) { return "wired" }
return "unknown"
}
weak var bridge: AdvancedJavaScriptBridge?
}
3. 相机和媒体处理器
// MARK: - 相机处理器
import AVFoundation
class CameraHandler: AdvancedJavaScriptBridge.MessageHandler, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
weak var viewController: UIViewController?
private var picker: UIImagePickerController?
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "scanQR":
scanQRCode(message, completion)
case "takePhoto":
takePhoto(message, completion)
case "pickImage":
pickImage(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown camera method"))
}
}
private func scanQRCode(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
requestCameraPermission {
self.scanQRCode(message, completion)
}
return
}
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.delegate = self
picker.cameraCaptureMode = .photo
picker.modalPresentationStyle = .fullScreen
// QR码检测
if UIImagePickerController.isSourceTypeAvailable(.camera) {
picker.sourceType = .camera
} else {
completion(createErrorResponse(message.id, error: "Camera not available"))
return
}
self.picker = picker
viewController?.present(picker!, animated: true)
// 存储完成回调
self.completionHandler = { result in
completion(result)
}
}
private func takePhoto(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let vc = viewController else {
completion(createErrorResponse(message.id, error: "No view controller"))
return
}
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.delegate = self
picker.allowsEditing = true
self.picker = picker
self.completionHandler = { result in
completion(result)
}
vc.present(picker, animated: true)
}
private func pickImage(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = message.params["maxCount"] as? Int ?? 1
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
picker.modalPresentationStyle = .fullScreen
self.picker = picker
self.completionHandler = { result in
completion(result)
}
viewController?.present(picker, animated: true)
}
// MARK: - UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
picker.dismiss(animated: true)
if let image = info[.editedImage] as? UIImage {
// 保存到临时文件
let tempDir = FileManager.default.temporaryDirectory
let fileName = UUID().uuidString + ".jpg"
let fileURL = tempDir.appendingPathComponent(fileName)
if let data = image.jpegData(compressionQuality: 0.8) {
try? data.write(to: fileURL)
let result: [String: Any] = [
"success": true,
"filePath": fileURL.path,
"fileSize": data.count,
"width": image.size.width,
"height": image.size.height,
"base64": data.base64EncodedString()
]
completionHandler?(createSuccessResponse(messageId, result: result))
}
}
}
// MARK: - PHPickerViewControllerDelegate
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
var images: [[String: Any]] = []
let group = DispatchGroup()
var tempFiles: [URL] = []
for result in results {
group.enter()
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
defer { group.leave() }
guard let image = image as? UIImage, error == nil else {
return
}
let tempDir = FileManager.default.temporaryDirectory
let fileName = UUID().uuidString + ".jpg"
let fileURL = tempDir.appendingPathComponent(fileName)
tempFiles.append(fileURL)
if let data = image.jpegData(compressionQuality: 0.8) {
try? data.write(to: fileURL)
let imageInfo: [String: Any] = [
"filePath": fileURL.path,
"fileSize": data.count,
"width": image.size.width,
"height": image.size.height,
"base64": data.base64EncodedString()
]
images.append(imageInfo)
}
}
}
group.notify(queue: .main) {
let result: [String: Any] = [
"success": true,
"images": images,
"count": images.count
]
self.completionHandler?(self.createSuccessResponse(self.messageId, result: result))
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
completionHandler?(createErrorResponse(messageId, error: "User cancelled"))
}
// MARK: - 权限处理
private func requestCameraPermission(completion: @escaping () -> Void) {
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
if granted {
completion()
} else {
self.completionHandler?(self.createErrorResponse(self.messageId, error: "Camera access denied"))
}
}
}
}
// MARK: - 辅助属性和方法
private var completionHandler: ((ResponseMessage) -> Void)?
private var messageId: String = ""
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
4. 位置服务处理器
// MARK: - 位置处理器
import CoreLocation
class LocationHandler: AdvancedJavaScriptBridge.MessageHandler, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var currentLocation: CLLocation?
private weak var bridge: AdvancedJavaScriptBridge?
init(bridge: AdvancedJavaScriptBridge?) {
self.bridge = bridge
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "getCurrentPosition":
getCurrentPosition(message, completion)
case "watchPosition":
watchPosition(message, completion)
case "clearWatch":
clearWatch(message, completion)
case "geocode":
geocodeAddress(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown location method"))
}
}
private func getCurrentPosition(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let options = message.params["options"] as? [String: Any] ?? [:]
let timeout = options["timeout"] as? TimeInterval ?? 10000
let maximumAge = options["maximumAge"] as? TimeInterval ?? 0
if let location = currentLocation, maximumAge == 0 || Date().timeIntervalSince(location.timestamp) < maximumAge {
let result = locationToDictionary(location)
completion(createSuccessResponse(message.id, result: result))
return
}
if CLLocationManager.locationServicesEnabled() {
locationManager.requestLocation()
startTimeout(timeout: timeout) { timedOut in
if timedOut {
completion(self.createErrorResponse(message.id, error: "Location request timed out"))
}
}
} else {
completion(createErrorResponse(message.id, error: "Location services disabled"))
}
}
private func watchPosition(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let watchId = UUID().uuidString
let options = message.params["options"] as? [String: Any] ?? [:]
let enableHighAccuracy = options["enableHighAccuracy"] as? Bool ?? false
if enableHighAccuracy {
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
} else {
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
}
locationManager.startUpdatingLocation()
// 存储watch ID
// 在实际实现中应该保存到某个管理器中
let result: [String: Any] = [
"watchId": watchId,
"success": true
]
completion(createSuccessResponse(message.id, result: result))
}
private func clearWatch(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let watchId = message.params["watchId"] as? String ?? ""
// 停止位置更新
locationManager.stopUpdatingLocation()
let result: [String: Any] = [
"watchId": watchId,
"success": true
]
completion(createSuccessResponse(message.id, result: result))
}
private func geocodeAddress(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let address = message.params["address"] as? String else {
completion(createErrorResponse(message.id, error: "Missing address"))
return
}
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { placemarks, error in
if let error = error {
completion(self.createErrorResponse(message.id, error: error.localizedDescription))
} else if let placemarks = placemarks, !placemarks.isEmpty {
let results = placemarks.compactMap { placemark -> [String: Any]? in
guard let location = placemark.location else { return nil }
var result: [String: Any] = [
"latitude": location.coordinate.latitude,
"longitude": location.coordinate.longitude,
"accuracy": location.horizontalAccuracy,
"altitude": location.altitude,
"timestamp": ISO8601DateFormatter().string(from: location.timestamp)
]
if let name = placemark.name {
result["name"] = name
}
if let thoroughfare = placemark.thoroughfare {
result["street"] = thoroughfare
}
if let locality = placemark.locality {
result["city"] = locality
}
if let administrativeArea = placemark.administrativeArea {
result["state"] = administrativeArea
}
if let postalCode = placemark.postalCode {
result["zip"] = postalCode
}
if let country = placemark.country {
result["country"] = country.name
}
return result
}
completion(self.createSuccessResponse(message.id, result: ["results": results]))
} else {
completion(self.createErrorResponse(message.id, error: "No results found"))
}
}
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
currentLocation = location
let result = locationToDictionary(location)
// 通知所有监听者
bridge?.publishEvent("locationUpdated", data: result)
// 停止单次定位请求
if manager.monitoredRegions.isEmpty {
manager.stopUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
bridge?.publishEvent("locationError", data: [
"error": error.localizedDescription,
"code": "LOCATION_ERROR"
])
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
let authStatus: [String: Any] = [
"status": statusToString(status),
"granted": status == .authorizedWhenInUse || status == .authorizedAlways
]
bridge?.publishEvent("authorizationStatusChanged", data: authStatus)
}
// MARK: - 辅助方法
private var timeoutTimer: Timer?
private func startTimeout(timeout: TimeInterval, completion: @escaping (Bool) -> Void) {
timeoutTimer?.invalidate()
timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeout / 1000.0, repeats: false) { _ in
completion(true)
}
}
private func locationToDictionary(_ location: CLLocation) -> [String: Any] {
return [
"latitude": location.coordinate.latitude,
"longitude": location.coordinate.longitude,
"accuracy": location.horizontalAccuracy,
"altitude": location.altitude,
"altitudeAccuracy": location.verticalAccuracy,
"speed": location.speed,
"heading": location.course,
"timestamp": ISO8601DateFormatter().string(from: location.timestamp)
]
}
private func statusToString(_ status: CLAuthorizationStatus) -> String {
switch status {
case .notDetermined: return "notDetermined"
case .restricted: return "restricted"
case .denied: return "denied"
case .authorizedAlways: return "authorizedAlways"
case .authorizedWhenInUse: return "authorizedWhenInUse"
@unknown default: return "unknown"
}
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
5. 集成到WebViewController
// MARK: - 增强的WebViewController
class EnhancedWebViewController: UIViewController {
// MARK: - Properties
private let webView = WKWebView()
private lazy var jsBridge = AdvancedJavaScriptBridge(webView: webView)
private var networkHandler: NetworkHandler?
private var cameraHandler: CameraHandler?
private var locationHandler: LocationHandler?
// MARK: - UI Components
private lazy var activityIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .large)
indicator.translatesAutoresizingMaskIntoConstraints = false
indicator.hidesWhenStopped = true
return indicator
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
setupHandlers()
loadInitialPage()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
cleanupHandlers()
}
// MARK: - Setup
private func setupWebView() {
view.backgroundColor = .systemBackground
// 配置WebView
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = []
webView.configuration = configuration
webView.navigationDelegate = self
webView.uiDelegate = self
webView.allowsBackForwardNavigationGestures = true
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// 添加加载指示器
view.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
private func setupHandlers() {
// 网络处理器
networkHandler = NetworkHandler()
networkHandler?.bridge = jsBridge
networkHandler?.startMonitoring()
// 相机处理器
cameraHandler = CameraHandler()
cameraHandler?.viewController = self
jsBridge.registerHandler("camera", handler: cameraHandler!)
// 位置处理器
locationHandler = LocationHandler(bridge: jsBridge)
jsBridge.registerHandler("location", handler: locationHandler!)
// 自定义处理器
jsBridge.registerHandler("analytics", handler: AnalyticsHandler())
jsBridge.registerHandler("payment", handler: PaymentHandler())
}
private func cleanupHandlers() {
networkHandler?.monitor?.cancel()
locationHandler?.locationManager.stopUpdatingLocation()
}
private func loadInitialPage() {
if let url = URL(string: "https://your-hybrid-app.com") {
webView.load(URLRequest(url: url))
activityIndicator.startAnimating()
}
}
// MARK: - 工具方法
func callJavaScript(_ function: String, parameters: [String: Any] = [:], completion: ((Any?) -> Void)? = nil) {
var jsCode = function
if !parameters.isEmpty {
let jsonData = try? JSONSerialization.data(withJSONObject: parameters, options: [])
let jsonString = String(data: jsonData ?? Data(), encoding: .utf8) ?? "{}"
jsCode = "\(function)(\(jsonString));"
}
webView.evaluateJavaScript(jsCode) { result, error in
if let error = error {
print("JavaScript execution error: \(error)")
}
completion?(result)
}
}
}
// MARK: - WKNavigationDelegate
extension EnhancedWebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
activityIndicator.startAnimating()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
activityIndicator.stopAnimating()
// 页面加载完成后初始化
let initCode = """
if (typeof JSBridge !== 'undefined') {
JSBridge.ready(function() {
// 通知页面应用已准备就绪
JSBridge.callNative('appReady', {
platform: 'iOS',
version: '\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0")'
});
// 初始化应用
if (typeof app !== 'undefined' && typeof app.init === 'function') {
app.init();
}
});
}
"""
webView.evaluateJavaScript(initCode)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
activityIndicator.stopAnimating()
showError(error.localizedDescription)
}
private func showError(_ message: String) {
let alert = UIAlertController(title: "加载失败", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "重试", style: .default) { _ in
webView.reload()
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
}
// MARK: - WKUIDelegate
extension EnhancedWebViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
completionHandler()
})
present(alert, animated: true)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "取消", style: .cancel) { _ in
completionHandler(false)
})
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
completionHandler(true)
})
present(alert, animated: true)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alert = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
alert.addTextField { textField in
textField.text = defaultText
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel) { _ in
completionHandler(nil)
})
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
completionHandler(alert.textFields?.first?.text)
})
present(alert, animated: true)
}
}
6. 分析和支付处理器示例
// MARK: - 分析处理器
class AnalyticsHandler: AdvancedJavaScriptBridge.MessageHandler {
private let analytics = AnalyticsManager.shared
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "trackEvent":
trackEvent(message, completion)
case "trackPageView":
trackPageView(message, completion)
case "setUserProperties":
setUserProperties(message, completion)
case "identifyUser":
identifyUser(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown analytics method"))
}
}
private func trackEvent(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let eventName = message.params["eventName"] as? String else {
completion(createErrorResponse(message.id, error: "Missing eventName"))
return
}
let properties = message.params["properties"] as? [String: Any] ?? [:]
analytics.track(event: eventName, properties: properties)
completion(createSuccessResponse(message.id, result: ["eventId": UUID().uuidString]))
}
private func trackPageView(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let pageName = message.params["pageName"] as? String else {
completion(createErrorResponse(message.id, error: "Missing pageName"))
return
}
let properties = message.params["properties"] as? [String: Any] ?? [:]
analytics.trackPageView(pageName: pageName, properties: properties)
completion(createSuccessResponse(message.id, result: ["success": true]))
}
private func setUserProperties(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let properties = message.params["properties"] as? [String: Any] ?? [:]
analytics.setUserProperties(properties)
completion(createSuccessResponse(message.id, result: ["success": true]))
}
private func identifyUser(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let userId = message.params["userId"] as? String else {
completion(createErrorResponse(message.id, error: "Missing userId"))
return
}
let traits = message.params["traits"] as? [String: Any] ?? [:]
analytics.identify(userId: userId, traits: traits)
completion(createSuccessResponse(message.id, result: ["success": true]))
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
// MARK: - 支付处理器
class PaymentHandler: AdvancedJavaScriptBridge.MessageHandler {
private let paymentService = PaymentService.shared
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "startPayment":
startPayment(message, completion)
case "checkPaymentStatus":
checkPaymentStatus(message, completion)
case "refund":
processRefund(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown payment method"))
}
}
private func startPayment(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let amount = message.params["amount"] as? Double,
let currency = message.params["currency"] as? String,
let orderId = message.params["orderId"] as? String else {
completion(createErrorResponse(message.id, error: "Missing required payment parameters"))
return
}
let paymentParams: [String: Any] = [
"amount": amount,
"currency": currency,
"orderId": orderId,
"userId": message.params["userId"] as? String ?? "",
"description": message.params["description"] as? String ?? ""
]
paymentService.startPayment(with: paymentParams) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let paymentResult):
let responseData: [String: Any] = [
"paymentId": paymentResult.paymentId,
"status": paymentResult.status,
"transactionId": paymentResult.transactionId
]
self?.completion(createSuccessResponse(message.id, result: responseData))
case .failure(let error):
self?.completion(createErrorResponse(message.id, error: error.localizedDescription))
}
}
}
}
private func checkPaymentStatus(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let paymentId = message.params["paymentId"] as? String else {
completion(createErrorResponse(message.id, error: "Missing paymentId"))
return
}
paymentService.checkStatus(for: paymentId) { result in
DispatchQueue.main.async {
switch result {
case .success(let status):
let responseData: [String: Any] = [
"status": status,
"updatedAt": ISO8601DateFormatter().string(from: Date())
]
completion(createSuccessResponse(message.id, result: responseData))
case .failure(let error):
completion(createErrorResponse(message.id, error: error.localizedDescription))
}
}
}
}
private func processRefund(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let paymentId = message.params["paymentId"] as? String,
let amount = message.params["amount"] as? Double else {
completion(createErrorResponse(message.id, error: "Missing refund parameters"))
return
}
paymentService.processRefund(paymentId: paymentId, amount: amount) { result in
DispatchQueue.main.async {
switch result {
case .success(let refundResult):
let responseData: [String: Any] = [
"refundId": refundResult.refundId,
"status": refundResult.status,
"amount": refundResult.amount
]
completion(createSuccessResponse(message.id, result: responseData))
case .failure(let error):
completion(createErrorResponse(message.id, error: error.localizedDescription))
}
}
}
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
7. 使用示例和测试
7.1 HTML页面示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hybrid App Test</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; }
button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; background: #007AFF; color: white; }
#log { background: #f5f5f5; padding: 10px; border-radius: 5px; height: 200px; overflow-y: scroll; margin-top: 20px; }
</style>
</head>
<body>
<h1>JavaScript桥接测试</h1>
<button onclick="testDeviceInfo()">获取设备信息</button>
<button onclick="testLocation()">获取位置</button>
<button onclick="testCamera()">拍照</button>
<button onclick="testNetwork()">网络请求</button>
<button onclick="testStorage()">测试存储</button>
<button onclick="testAnalytics()">分析事件</button>
<button onclick="testPayment()">测试支付</button>
<div id="log"></div>
<script>
// 等待桥接就绪
JSBridge.ready(function() {
log('JSBridge 已就绪');
// 订阅事件
JSBridge.subscribe('networkStatusChanged', function(data) {
log('网络状态变化: ' + JSON.stringify(data));
});
JSBridge.subscribe('locationUpdated', function(data) {
log('位置更新: ' + JSON.stringify(data));
});
});
function log(message) {
var logDiv = document.getElementById('log');
var timestamp = new Date().toLocaleTimeString();
logDiv.innerHTML += '[' + timestamp + '] ' + message + '<br>';
logDiv.scrollTop = logDiv.scrollHeight;
// 同时发送到原生日志
JSBridge.callNative('log', { level: 'info', message: message });
}
function testDeviceInfo() {
JSBridge.callNative('device.getInfo', {}, function(success, result, error) {
if (success) {
log('设备信息: ' + JSON.stringify(result, null, 2));
} else {
log('设备信息错误: ' + error);
}
});
}
function testLocation() {
JSBridge.callNative('location.getCurrentPosition', {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}, function(success, result, error) {
if (success) {
log('当前位置: 纬度' + result.latitude + ', 经度' + result.longitude);
} else {
log('位置错误: ' + error);
}
});
}
function testCamera() {
JSBridge.callNative('camera.takePhoto', {}, function(success, result, error) {
if (success) {
log('拍照成功: ' + result.filePath);
// 可以显示图片
if (result.base64) {
var img = document.createElement('img');
img.src = 'data:image/jpeg;base64,' + result.base64;
img.style.maxWidth = '100%';
document.body.appendChild(img);
}
} else {
log('拍照错误: ' + error);
}
});
}
function testNetwork() {
// 测试网络状态
JSBridge.callNative('network.getStatus', {}, function(success, result) {
log('网络状态: ' + JSON.stringify(result));
});
// 测试HTTP请求
JSBridge.callNative('network.fetch', {
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: 'GET'
}, function(success, result, error) {
if (success) {
log('网络请求成功: ' + JSON.stringify(result.body));
} else {
log('网络请求错误: ' + error);
}
});
}
function testStorage() {
// 测试存储
JSBridge.callNative('storage.setItem', {
key: 'testKey',
value: 'Hello from JS!'
}, function(success) {
if (success) {
JSBridge.callNative('storage.getItem', {
key: 'testKey'
}, function(success, result) {
log('存储值: ' + result.value);
});
}
});
}
function testAnalytics() {
JSBridge.callNative('analytics.trackEvent', {
eventName: 'button_clicked',
properties: {
button_id: 'test_button',
user_type: 'premium'
}
}, function(success, result) {
log('分析事件发送: ' + JSON.stringify(result));
});
JSBridge.callNative('analytics.setUserProperties', {
properties: {
user_age: 25,
user_city: 'Beijing'
}
});
}
function testPayment() {
JSBridge.callNative('payment.startPayment', {
amount: 9.99,
currency: 'USD',
orderId: 'order_' + Date.now(),
description: 'Test Payment'
}, function(success, result, error) {
if (success) {
log('支付发起成功: ' + JSON.stringify(result));
} else {
log('支付错误: ' + error);
}
});
}
// 批量测试
function testBatch() {
var calls = [
{ method: 'device.getInfo' },
{ method: 'network.getStatus' },
{ method: 'storage.getItem', params: { key: 'testKey' } }
];
JSBridge.batchCall(calls, function(results) {
log('批量调用结果: ' + JSON.stringify(results, null, 2));
});
}
</script>
</body>
</html>
8. 性能优化和安全考虑
8.1 性能监控
// MARK: - 性能监控处理器
class PerformanceHandler: AdvancedJavaScriptBridge.MessageHandler {
private var startTime: Date?
private var metrics: [String: Any] = [:]
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
switch message.method {
case "startMeasure":
startMeasure(message, completion)
case "endMeasure":
endMeasure(message, completion)
case "getMetrics":
getMetrics(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown performance method"))
}
}
private func startMeasure(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let measureName = message.params["name"] as? String ?? "default"
startTime = Date()
metrics[measureName] = ["startTime": Date().timeIntervalSince1970]
completion(createSuccessResponse(message.id, result: ["measureId": measureName]))
}
private func endMeasure(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let measureName = message.params["name"] as? String ?? metrics.keys.first,
let startInfo = metrics[measureName] as? [String: Double],
let startTime = startInfo["startTime"] else {
completion(createErrorResponse(message.id, error: "No active measure"))
return
}
let endTime = Date().timeIntervalSince1970
let duration = endTime - startTime
metrics[measureName] = [
"startTime": startTime,
"endTime": endTime,
"duration": duration
]
let result: [String: Any] = [
"measure": measureName,
"duration": duration * 1000, // 毫秒
"unit": "ms"
]
completion(createSuccessResponse(message.id, result: result))
}
private func getMetrics(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let filteredMetrics: [[String: Any]] = metrics.compactMap { name, info in
guard let metricInfo = info as? [String: Double] else { return nil }
return [
"name": name,
"duration": (metricInfo["endTime"] ?? 0) - (metricInfo["startTime"] ?? 0),
"startTime": metricInfo["startTime"] ?? 0,
"endTime": metricInfo["endTime"] ?? 0
]
}
completion(createSuccessResponse(message.id, result: ["metrics": filteredMetrics]))
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
8.2 安全处理器
// MARK: - 安全处理器
class SecurityHandler: AdvancedJavaScriptBridge.MessageHandler {
private let keychain = KeychainService()
func handleMessage(_ message: RequestMessage, completion: @escaping (ResponseMessage) -> Void) {
// 验证消息来源
guard validateMessageOrigin(message) else {
completion(createErrorResponse(message.id, error: "Invalid message origin"))
return
}
// 验证方法权限
guard hasPermission(for: message.method) else {
completion(createErrorResponse(message.id, error: "Permission denied"))
return
}
switch message.method {
case "encrypt":
encryptData(message, completion)
case "decrypt":
decryptData(message, completion)
case "validateToken":
validateToken(message, completion)
case "biometricAuth":
authenticateWithBiometrics(message, completion)
default:
completion(createErrorResponse(message.id, error: "Unknown security method"))
}
}
private func validateMessageOrigin(_ message: RequestMessage) -> Bool {
// 验证消息来源的安全性
let allowedDomains = ["https://yourapp.com", "https://api.yourapp.com"]
let origin = message.params["origin"] as? String ?? ""
return allowedDomains.contains { origin.hasSuffix($0) }
}
private func hasPermission(for method: String) -> Bool {
// 检查用户权限
let permissions = UserDefaults.standard.array(forKey: "security_permissions") as? [String] ?? []
return permissions.contains(method)
}
private func encryptData(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let data = message.params["data"] as? String else {
completion(createErrorResponse(message.id, error: "Missing data"))
return
}
do {
let encrypted = try keychain.encrypt(data: data)
let result: [String: Any] = [
"encryptedData": encrypted,
"algorithm": "AES256"
]
completion(createSuccessResponse(message.id, result: result))
} catch {
completion(createErrorResponse(message.id, error: error.localizedDescription))
}
}
private func decryptData(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let encryptedData = message.params["encryptedData"] as? String else {
completion(createErrorResponse(message.id, error: "Missing encryptedData"))
return
}
do {
let decrypted = try keychain.decrypt(encryptedData: encryptedData)
completion(createSuccessResponse(message.id, result: ["data": decrypted]))
} catch {
completion(createErrorResponse(message.id, error: error.localizedDescription))
}
}
private func validateToken(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
guard let token = message.params["token"] as? String else {
completion(createErrorResponse(message.id, error: "Missing token"))
return
}
// JWT令牌验证
let isValid = validateJWT(token: token)
let expiresIn = calculateTokenExpiry(token)
let result: [String: Any] = [
"valid": isValid,
"expiresIn": expiresIn,
"needsRefresh": expiresIn < 300 // 5分钟
]
completion(createSuccessResponse(message.id, result: result))
}
private func authenticateWithBiometrics(_ message: RequestMessage, _ completion: @escaping (ResponseMessage) -> Void) {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
let result: [String: Any] = [
"success": false,
"reason": error?.localizedDescription ?? "Biometrics not available"
]
completion(createSuccessResponse(message.id, result: result))
return
}
let reason = message.params["reason"] as? String ?? "Please authenticate to continue"
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { [weak self] success, authError in
DispatchQueue.main.async {
let result: [String: Any] = [
"success": success,
"error": authError?.localizedDescription
]
self?.completion(createSuccessResponse(message.id, result: result))
}
}
}
private func validateJWT(token: String) -> Bool {
// 简化的JWT验证
let components = token.components(separatedBy: ".")
guard components.count == 3 else { return false }
// 在实际应用中应该验证签名
do {
let payloadData = Data(base64Encoded: components[1]
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/") + "==")
let payload = try JSONSerialization.jsonObject(with: payloadData!) as? [String: Any]
let exp = payload?["exp"] as? TimeInterval
return exp.map { $0 > Date().timeIntervalSince1970 } ?? false
} catch {
return false
}
}
private func calculateTokenExpiry(_ token: String) -> TimeInterval {
let components = token.components(separatedBy: ".")
guard components.count == 3,
let payloadData = Data(base64Encoded: components[1]
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/") + "=="),
let payload = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
let exp = payload["exp"] as? TimeInterval else {
return 0
}
return max(0, exp - Date().timeIntervalSince1970)
}
private func createSuccessResponse(_ id: String, result: Any) -> ResponseMessage {
return ResponseMessage(id: id, success: true, result: result, error: nil, timestamp: Date())
}
private func createErrorResponse(_ id: String, error: String) -> ResponseMessage {
return ResponseMessage(id: id, success: false, result: nil, error: error, timestamp: Date())
}
}
总结
这个高级JavaScript桥接实现提供了:
🎯 核心特性
-
双向通信: 完整的请求-响应模型
-
类型安全: Codable协议支持
-
异步处理: 完整的回调支持
-
事件订阅: 发布/订阅模式
🔧 高级功能
-
批量调用: 高效的批量操作
-
性能监控: 内置性能测量
-
安全验证: 消息来源和权限验证
-
错误处理: 统一的错误处理机制
📱 原生功能集成
-
设备信息: 完整的设备API
-
网络服务: HTTP请求和文件传输
-
媒体处理: 相机、相册访问
-
位置服务: GPS定位和地理编码
-
安全认证: 生物识别和数据加密
-
分析跟踪: 事件和用户行为分析
-
支付集成: 支付流程管理
🚀 使用建议
-
生产环境: 添加更多的错误处理和重试机制
-
安全性: 实现完整的消息签名验证
-
性能: 添加消息队列和限流机制
-
测试: 编写全面的单元测试和集成测试