应对苹果公司的号召,2020年还是要把之前老项目的UIWebView都替换成WKWebView。
单纯换View倒也不难,除了代理方法有点区别之外,加载网页的使用方式都是类似的。
但现在越来越多应用都采用混合开发的模式,所以就需要Native与JS之间进行良好的通信。
之前UIWebView与JS通信是采用JavaScriptCore来实现的
通过给JSContext动态注入OC对象来实现JS调用Native
由于以后苹果不允许使用UIWebView我这里也就不多讲述其实现了
主要还是说一说WKWebView是如何与JS之间交互的!
开讲之前
今天我们从两个方面来讲述OC与JS交互:
1、通过原生的WKWebViewConfiguration
2、通过使用WebViewJavascriptBridge
通过Native调用JS的方式,没有任何争议,几乎都是用WebView提供的
[webView evaluateJavaScript:@"" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
}];
这里我们重点讨论JS如何调用Native
WKWebViewConfiguration、WKUserContentController
WKWebViewConfiguration对象有一个很关键的属性参数userContentController
我们可以把它当做内容交互控制器,可以自己注入JS代码及JS调用原生方法注册
- (void)addScriptMessageHandler:(id )scriptMessageHandler name:(NSString *)name;
addScript需要与remove成对出现,在dealloc时需要注意调用
- (void)removeScriptMessageHandlerForName:(NSString *)name;
在JS里我们可以通过
window.webkit.messageHandlers.<注册的方法名>.postMessage(<需要传递的参数>);
的方式来调用我们在Native端注册的方法
同时Native会通过WKScriptMessageHandler
代理方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
接收JS传递过来的参数,参数内容都封装在WKScriptMessage
对象中
其中,name
为注册的方法名,body
为传递的参数对象
由于只能接收一个参数,所以JS通常采取Object转JSON字符串的形式传递多参数,Native这边用NSDictionary接收
WebViewJavascriptBridge
这是github上非常火的一个桥接库https://github.com/marcuswestin/WebViewJavascriptBridge有12.9k的Star
看了看他的源码,整个处理的非常优雅
主要是利用iframe设置src的方式通知Native完成通信过程
所以从UIWebView到WKWebView的改动几乎很小,并且还支持相互的通信之间的回调,可以说是非常全面的Bridge了
我们在这里简单的分析一下这个库是如何实现通信的
开始之前我们先看一下这个库的文件结构
整个库总共也就八个文件,算是十分精简也是十分清晰了
核心代码在 WebViewJavascriptBridge_JS与 WebViewJavascriptBridgeBase中
前者负责实现注入JS的通信解析对象
后者负责实现Native解析JS信息的对象
其余两个类文件是用来扩展到UIWebView和WKWebView的,主要是实现对应的协议方法,用作拦截URL
JS调用Native
打开WebView页面时,默认通过iframe设置
WVJBIframe.src = 'https://__bridge_loaded__';
客户端在拦截到初始化的指令时会通过这两步进行预设好的JS注入
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
}
......
- (void)injectJavascriptFile {
NSString *js = WebViewJavascriptBridge_js();
[self _evaluateJavascript:js];
}
初始化工作做好之后,按照流程就是Native端的注册方法
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"Response from testObjcCallback");
}];
等待JS端的调用
JS通过bridge对象的callHandler方法
var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
callbackButton.innerHTML = 'Fire testObjcCallback'
callbackButton.onclick = function(e) {
bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
log('JS got response', response)
})
}
......
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message['callbackId'] = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = 'https://__wvjb_queue_message__/';
}
拦截URL变化主要是通过WebView的delegate获取
/// UIWebView 通过
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
/// WKWebView 通过
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
设置了src为https://__wvjb_queue_message__/
,Native拦截之后通过获取sendMessageQueue
中的message
完成方法名的提取和参数的提取
[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
if (error != nil) {
NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
}
[_base flushMessageQueue:result];
}];
通过messageJSONString中的信息匹配到之前注册的方法名和block,调用执行。
如果Native注册的方法中有返回responseCallBack信息,则将返回的信息参数与JS提供的callBackId
组成新的对象,通知JS解析,JS通过callBackId
找到对应方法并传递参数执行
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
以上就是完整的JS调用Native流程
Native调用JS
反过来,Native调用JS,其实双方注册方法和调用方法都是类似的,整个流程反一下就可以了,具体我这里就不细说了,只要前面的理解了,后面就很容易想明白了,可以参考源码Demo。
总结一下
1、JS通知Native信息变化通过
iframe.src='https://__bridge_loaded__/' // 初始化
iframe.src='https://__wvjb_queue_message__/' // 发送消息
2、Native通知JS传递参数通过
[webView evaluateJavaScript:@"" completionHandler:^(id _Nullable result, NSError * _Nullable error) {}];
整个交互过程依赖双方存储注册方法信息,并通过方法名和参数来完成跨界调用
这个第三方库考验开发者要有一些基础的前端开发经验,否则对于里面近半数的JS源码可能不太理解,重点是设置iframe.src
以上就是本次我想分享的关于WKWebView与JS交互的内容
ps:内容建议结合源码Demo一起食用,更容易理清思路
希望对你有帮助