iOS JSBridge實現
原文地址:https://mp.weixin.qq.com/s/U1GMo4GIO9BiHjGbt8W-rw
本文已上傳git:
iOS-Tech
https://github.com/sugc/iOS-Tech
jsbridge是實現JS和Navite應用交互的一種方式,分為JS調Native和Native調JS.這邊以WKWebView為例講一下JSBridge的實現。
demo地址
https://github.com/sugc/iOS-JSBridge
Native調H5
Native調JS的方式比較簡單, 通過以下方法可以直接執行一段JS腳本,並獲取返回值
public func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo? = nil, in contentWorld: WKContentWorld, completionHandler: ((Result<Any, Error>) -> Void)? = nil)
H5調Navite
JS調Native本質都是Native通過攔截H5的回調,解析之后根據參數執行native代碼,再回調結果到JS. 目前所了解到的正常主要有以下兩種方式。
1,攔截跳轉url
<!--JS-->
var iframe = document.createElement('iframe'); iframe.style.with = '1px'; iframe.style.height = '1px'; iframe.style.display = 'none'; iframe.src = url; document.body.appendChild(iframe);
//Native
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.request.url?.scheme == "jsbridge" {
// 攔截自定義scheme,進行jsbridge處理
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
2,攔截prompt回調
//JS var returenValue = prompt(url);
//Native func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { if prompt.hasPrefix("JSBridge://") { // 攔截自定義scheme,進行jsbridge處理 } }
目前主流應該是使用第一種方式,第二種方式實際是系統定義好的調用Native輸入框的API,這種使用方式不太符合規范,但好處是可以直接將參數轉成string直接返回。而第一種方式需要定義回調JS用來返回Native的執行結果。
如何設計一個好用的JSBridge
這邊通過攔截跳轉Url的方式來設計一個JSBridge實現JS調Native, 主要包含以下幾塊
- 協議定義
- JS調Native方法
- Native解析執行模塊
- Native回調JS方法
1 首先看看協議怎么定,協議包含以下兩部分
攔截scheme, 如JSBridge, 只要不和常見的scheme沖突即可
參數定義:
包含以下幾部分部分,參數包成字典,以JSONString的形式拼在url參數中Native方法名
JS調Native參數
Native如何回調JS的參數示例:(未編碼) JSBridge://methodName?params={nativeParam={},callback='xxx'}
2 Native解析代碼就不展開說了,iOS開發都熟,具體可以看看demo。下面說說JS部分如何設計。
Native回調JS的關鍵在於,如何去保存和尋找每次JS調用的回調方法。可以通過JS側用一個字典保存這些回調,然后將key傳給Native, Native執行完畢之后,再將返回值和key一起回調到JS。
var responseCallbacks = {} var messageID = 0 <!--調用Native方法--> function callHandler(handlerName, data, responseCallback) { <!--保存JS回調,由於可能多次調用,因此每次key都要不同--> if (responseCallback) { var callbackId = messageID + handlerName; messageID = messageID + 1 responseCallbacks[callbackId] = responseCallback args.push(callbackId) } } <!--Native回調JS--> function callBackJS(methodKey, param){ callBack = responseCallbacks[methodKey] if (callback) { callback(param) } }