自己最近重新梳理了下iOS 中 Hybrid 交互方面的知識,這里簡單記錄一下:
環(huán)境說明:
- Web容器:WKWebView
- iOS10.0以上
- Swift: 5.0
術(shù)語說明:
-
JS: 指H5前端層面 -
Native: 指iOS原生層面
1.通信方式選擇
JS與原生交互的前提是保證JS與Native能進行相互通信,這里選擇的通信方案是通過注入JS對象的方式來實現(xiàn)。
具體實現(xiàn)方式是Native在創(chuàng)建好wkwebview容器后向其注入 JSBridge 對象。后續(xù)消息收發(fā)均通過注入的JSBridge對象進行。
webView.configuration.userContentController.add(self, name: "JSBridge")
注入成功后,可以在web瀏覽器中控制臺輸入window.webkit.messageHandlers.JSBridge 進行查看

其他幾種通信方案及詳細說明請看
從零收拾一個hybrid框架(一)-- 從選擇JS通信方案開始
需要注意的地方:
直接使用addScriptMessageHandler: name方法會存在內(nèi)存泄漏問題, 解決辦法可以參考:iOS 解決WKWebView中WKScriptMessageHandler方法引起內(nèi)存不釋放問題
2.消息結(jié)構(gòu)約定
需要Native與JS雙方約定一個消息結(jié)構(gòu),作為通信時消息傳遞的規(guī)范:
這里約定消息結(jié)構(gòu)如下:
//js
{
action: "foo", //兩端約定的方法或協(xié)議名
data: {} //傳輸數(shù)據(jù),json對象類型
callbackId: "xxxx-xxxxx-xxx", //消息回調(diào)id
}
//swift
class JSMessage: NSObject {
var action: String
var data: Dictionary<String, Any>?
var callbackId: String
}
其中callbackId 主要用于當(dāng)該消息有響應(yīng)數(shù)據(jù)時,將callbackId與傳入的回調(diào)方法進行綁定并寫入當(dāng)前環(huán)境的map中。當(dāng)獲取到回調(diào)數(shù)據(jù)后, 再從map中獲取保存的回調(diào)方法,并將回調(diào)數(shù)據(jù)傳入該方法。
3.具體通信流程
3.1 JS向Native發(fā)送消息
//js
function Bridge() {
this.sendMsg = function(msg) {
// TODO: 判斷不同平臺,目前只處理iOS
window.webkit.messageHandlers.JSBridge.postMessage(msg);
}
}
3.2 Native接收消息
//swift
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("收到JS消息: \(message.name), \(message.body)")
handleJSMsg(message)
}
/// 處理H5發(fā)送過來的消息
private func handleJSMsg(_ msg: WKScriptMessage) {
if !isSupportMsg(msg) { return }
if let body = msg.body as? Dictionary<String, Any>,
let action = body["action"] as? String,
let data = body["data"] as? Dictionary<String, Any>,
let callbackId = body["callbackId"] as? String {
let jsMsg = JSMessage(action: action, data: data, callbackId: callbackId)
//消息分發(fā)
dispatchMsg(jsMsg)
}
}
3.3 Native向JS發(fā)送消息
//swift
private func sendMsg(_ msg: JSMessage) {
let msgDict: [String : Any] = [
"action": msg.action,
"data": msg.data ?? "",
"callbackId": msg.callbackId
]
let jsonString = msgDict.formatJSON() ?? ""
let js = """
bridge.handleNativeMsg(\(jsonString))
"""
evaluateJavaScript(js)
}
private func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) {
print("調(diào)用js方法:\(javaScriptString)")
DispatchQueue.main.async {[weak self] in
self?.webView?.evaluateJavaScript(javaScriptString, completionHandler: completionHandler)
}
}
3.4 JS接收消息
//js
this.handleNativeMsg = function(msg) {
if (msg.action == "handleNativeResponse") {
this.handleNativeResponse(msg);
return;
}
let functionName = "bridgeMsgListener." + msg.action;
let func = eval(functionName);
// 消息轉(zhuǎn)發(fā)
new func(msg);
}
完整的時序圖如下

4.回調(diào)處理
針對一些異步操作或需要獲取響應(yīng)狀態(tài)的操作, 例如網(wǎng)絡(luò)請求,我們需要在對方執(zhí)行完畢后獲取到對應(yīng)的響應(yīng)數(shù)據(jù)。 針對這種情況,我們可以在發(fā)送message時申明兩個特殊的action:
handleH5Response用于表示這是一條來自h5側(cè)的響應(yīng)數(shù)據(jù),native需要進行回調(diào)處理handleNativeResponse用于表示這是一條來自native側(cè)的響應(yīng)數(shù)據(jù),h5需要進行回調(diào)處理
舉例說明: 例如,JS調(diào)用Native的一個網(wǎng)絡(luò)請求request方法,在Native獲取到網(wǎng)絡(luò)請求響應(yīng)數(shù)據(jù)responseData后,需要將responseData 返回給JS側(cè);
- JS發(fā)送消息:其中
callbackId是由JS生成一個的UUID字符串
{
action: "request",
data: {
url: "https://www.baidu.com"
},
callbackId: "xxxx-xxxxx-xx",
}
- Native收到消息后,解析消息內(nèi)容,并在本地發(fā)起網(wǎng)絡(luò)請求,在獲取到響應(yīng)數(shù)據(jù)
responseData后,封裝成JSMessage對象發(fā)送給
{
action: "handleNativeResponse",
data: responseData,
callbackId: "xxxx-xxxxx-xx",
}
- JS在
handleNativeResponse方法中獲取到msg消息中的callbackId及data響應(yīng)數(shù)據(jù),然后進行后續(xù)的回調(diào)操作
// 回調(diào)處理
this.handleNativeResponse = function(msg) {
let callbackFunction = this.callBackMaps[msg.callbackId];
if (callbackFunction != undefined && callbackFunction != null) {
this.callBackMaps.delete(msg.callbackId);
callbackFunction(msg.data);
}
}
5.最后
補個簡單的Demo: Demo
PS: 由于本人前端知識淺薄,demo中js部分建議根據(jù)實際需要進行改動。
后續(xù)應(yīng)該會增加一些其他的常用功能:
- 離線加載(從
bundle、沙盒加載html文件) - 逐步實現(xiàn)類似微信小程序的API功能
- 增加結(jié)構(gòu)配置項,如
tabbar、導(dǎo)航欄樣式 等...
參考鏈接:
從零收拾一個hybrid框架(一)-- 從選擇JS通信方案開始
從零收拾一個hybrid框架(二)-- WebView容器基礎(chǔ)功能設(shè)計思路
WebViewJsBridge-iOS 做一個好用的橋接庫