背景
- h5和native 交互代碼冗余 不清晰 多人開(kāi)發(fā)時(shí)效率地下
目的
- 為了尋找更搞笑的編程開(kāi)發(fā)方式蹬跃,節(jié)省代碼量木柬,以及多人開(kāi)發(fā)的效率
過(guò)程探索
JavaScriptCore
- 官方解釋
The JavaScriptCore Framework provides the ability to evaluate JavaScript programs from within Swift, Objective-C, and C-based apps. You can use also use JavaScriptCore to insert custom objects to the JavaScript environment.
JavaScriptCore框架提供了從Swift、Objective-C和基于c的應(yīng)用程序中評(píng)估JavaScript程序的能力府喳。您還可以使用JavaScriptCore將定制對(duì)象插入到JavaScript環(huán)境中落塑。
蘋(píng)果爸爸在iOS7.0以后推出的官方庫(kù),目前看起來(lái)是適用于UIWebView(官方以不推薦使用)
下面說(shuō)一下使用方法
關(guān)鍵詞: JSContext JSValue JSExport
- JSExport
繼承并申明你需要到protocol - 注入到JS里到function
@objc protocol SwiftJavaScriptProtocol: JSExport {
func test(_ value: String?)
// js調(diào)用App的功能后 App再調(diào)用js函數(shù)執(zhí)行回調(diào)
func callHandler(handleFuncName: String)
var stringCallback: (@convention(block) (String) -> String)? { get set }
}
申明一個(gè)繼承于NSObjectt的class, 并實(shí)現(xiàn)你的protocol
class SwiftJavaScriptModel: NSObject, SwiftJavaScriptProtocol {
weak var jsContext: JSContext? //js 的執(zhí)行環(huán)境盛泡,調(diào)用js 或者注入js
var stringCallback: (@convention(block) (String) -> String)? // swift里想要讓JS能夠調(diào)用我們的clourse,一定要用這個(gè)關(guān)鍵字
func test(_ value: String?) {
print("js call test(_ value: \(value ?? ""))")
}
func callHandler(handleFuncName: String) {
let jsHandlerFunc = self.jsContext?.objectForKeyedSubscript("\(handleFuncName)")
let dict = ["name": "sean", "age": 18] as [String : Any]
jsHandlerFunc?.call(withArguments: [dict])
}
override init() {
super.init()
self.stringCallback = {
[weak self] value in
print("SwiftJavaScriptModel stringCallback \(value)")
return value.appending("this is append string")
}
}
}
最后椎工,通過(guò)UIWebView的特性饭于,獲取JSContext,并將我們的function注入到上下文
extension JSCoreVC: UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
if let jsContext = webView.value(forKeyPath:"documentView.webView.mainFrame.javaScriptContext") as? JSContext {
let model = SwiftJavaScriptModel()
model.jsContext = jsContext
jsContext.setObject(model, forKeyedSubscript: ("WebViewJavascriptBridge" as NSString)) //注入
jsContext.evaluateScript(self.htmlContent)
jsContext.exceptionHandler = {
[weak self] (context, exception) in
guard let self = self else { return }
print("exception:", exception)
}
}
}
}
-
WebViewJavascriptBridge
注意蜀踏,這里注入的名字即是JS調(diào)用的名字维蒙,JS調(diào)用我們的函數(shù)和自己的使用方式一樣,舉例如下
<div class="btn-block" onclick="WebViewJavascriptBridge.test('this is toast')">
test
</div>
<div class="btn-block" onclick="appStringCallbackFunc('this is toast')">
stringCallback
</div>
<script type="text/javascript">
function appStringCallbackFunc(arg) {
let value = WebViewJavascriptBridge.stringCallback('this is toast')
document.getElementById('js-content').innerHTML = "App調(diào)用js回調(diào)函數(shù)啦, 返回 ->" + value;
}
</script>
看起來(lái)很方便吧果覆,app -> JS 也很方便
let context = JSContext()
let _ = context?.evaluateScript("var triple = (value) => value + 3") //注入
let returnV = context?.evaluateScript("triple(3)") // 調(diào)用
print("__testValueInContext --- returnValue = \(returnV?.toNumber())")
WKWebView
- 官方解釋
You can make POST requests with httpBody content in a WKWebView.
After creating a new WKWebView object using the init(frame:configuration:) method, you need to load the web content. Use the loadHTMLString(:baseURL:) method to begin loading local HTML files or the load(:) method to begin loading web content. Use the stopLoading() method to stop loading, and the isLoading property to find out if a web view is in the process of loading. Set the delegate property to an object conforming to the WKUIDelegate protocol to track the loading of web content. See Listing 1 for an example of creating a WKWebView programmatically.
你可以在WKWebView中用httpBody內(nèi)容發(fā)出POST請(qǐng)求颅痊。
在使用init(frame:configuration:)方法創(chuàng)建一個(gè)新的WKWebView對(duì)象之后,您需要加載web內(nèi)容局待。使用loadHTMLString(:baseURL:)方法開(kāi)始加載本地HTML文件斑响,或使用load(:)方法開(kāi)始加載web內(nèi)容。使用stopLoading()方法停止加載钳榨,使用isLoading屬性查明web view是否在加載過(guò)程中舰罚。將委托屬性設(shè)置為符合WKUIDelegate協(xié)議的對(duì)象,以跟蹤web內(nèi)容的加載薛耻。清單1給出了以編程方式創(chuàng)建WKWebView的示例营罢。
WKWebView關(guān)鍵詞
WKUIDelegate WKNavigationDelegate WKScriptMessageHandler
/*
實(shí)現(xiàn)原理:
1、JS與iOS約定好xdart協(xié)議饼齿,用作JS在調(diào)用iOS時(shí)url的scheme饲漾;
2、JS拿到的url:(xdart://lot_detail?id=123)缕溉;
3考传、iOS的WKWebView在請(qǐng)求跳轉(zhuǎn)前會(huì)調(diào)用-webView:decidePolicyForNavigationAction:decisionHandler:方法來(lái)確認(rèn)是否允許跳轉(zhuǎn);
4证鸥、iOS在此方法內(nèi)截取xdart協(xié)議獲取JS傳過(guò)來(lái)的數(shù)據(jù)僚楞,執(zhí)行內(nèi)部schema跳轉(zhuǎn)邏輯
5勤晚、通過(guò)decisionHandler(.cancel)可以設(shè)置不允許此請(qǐng)求跳轉(zhuǎn) decisionHandler一定要調(diào)用
*/
//! WKWeView在每次加載請(qǐng)求前會(huì)調(diào)用此方法來(lái)確認(rèn)是否進(jìn)行請(qǐng)求跳轉(zhuǎn)
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {return}
guard let scheme = url.scheme else {return}
if scheme == "xdart" {
// THSchemeManager.handleScheme(url.absoluteString)
}
decisionHandler(.allow)
}
WKUIDelegate 基于JS系統(tǒng)的幾個(gè)內(nèi)部方法 實(shí)現(xiàn)一下方法要調(diào)用對(duì)應(yīng)的completionHandler,否則崩潰
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
// call toast
completionHandler()
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
// call alert
completionHandler(true)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
// call input
completionHandler("this is a message")
}
WKScriptMessageHandler 重點(diǎn)來(lái)了泉褐,這是蘋(píng)果爸爸推薦使用的JS交互
let content = WKUserContentController()
content.add(self, name: "artproFunc") //此方法會(huì)造成循環(huán)引用运翼,注意時(shí)機(jī)釋放
let config = WKWebViewConfiguration()
config.userContentController = content
wkwebV = WKWebView.init(frame: self.view.bounds, configuration: config)
- 這里往content里注入的是JS調(diào)用APP的函數(shù)
JS通過(guò)window.webkit.messageHandlers.artproFunc.postMessage()給artproFunc發(fā)送消息
我們的解析在此方法里
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name != self.applicationName { return }
if let body = message.body as? [String: Any],
let method = body["method"] as? String {
let aSel = NSSelectorFromString(method)
if !self.canPerformAction(aSel, withSender: nil) {
return
}
let para = body["parameter"]
let callback = body["callback"]
}
}
以我目前的項(xiàng)目為例,我們是將 artproFunc當(dāng)作了一個(gè)通道兴枯,所有的function都走message.body分發(fā)出來(lái)血淌,所以會(huì)有switch case 解析 body中的method,然后再進(jìn)行不同的方法分發(fā)财剖。
缺點(diǎn)
- 代碼冗余
- 字符串分發(fā)悠夯,容易出錯(cuò)
函數(shù)格式各式各樣,多人開(kāi)發(fā)躺坟,他人不好接手
artpro_userContent.png
WebViewJavascriptBridge
最近研究JS和iOS native交互沦补,偶然發(fā)現(xiàn)的庫(kù)發(fā)現(xiàn)github上用的人也不少,感覺(jué)還不錯(cuò)的樣子,就研究了下使用方法
看起來(lái)很簡(jiǎn)單的樣子
bridge = WebViewJavascriptBridge.init(webV)
// js call app
bridge.registerHandler("testCallHandler") { [weak self](data, callback) in
guard let self = self else { return }
callback?("Response from testCallHandler")
}
// app call js
self.bridge.callHandler("testJavascriptHandler", data: ["foo": "before ready"])
WebViewJavascriptBridge 原理將在下篇文章中剖析
參考文檔1