主要作用
WebViewJavascriptBridge
是一个 OC 与 JS 消息分发工具。OC 可以通过向 WebViewJavascriptBridge
监听 JS 的消息,也可以向 JS 主动发送消息;JS 也同样支持监听 OC 消息和主动向 OC 发送消息。
基础原理
OC to JS
主要通过 WKWebView
的实例方法
- (void)evaluateJavaScript:(NSString *)javaScriptString
completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable))completionHandler;
比如:通过 JS 调用 UIAlertController
弹窗。
- (void)test {
[self.webView evaluateJavaScript:@"alert(\"Testing Alerts\");"
completionHandler:^(id _Nullable res, NSError * _Nullable error) {
NSLog(@"%@ %@", res, error);
}];
}
注意:js 调用 alert 方法,会触发 WKUIDelegate
协议的 webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:
方法,需要在方法中实现对应的弹窗逻辑。
通过这种方式,可以做到 OC 主动向 JS 发送消息,并且还可以收到来着 JS 的回调。
JS to OC
js 通过修改 iframe 的 src 来触发 OC 的 WKNavigationDelegate
协议,比如
JS 触发
var WVJBIframe = document.createElement('iframe');
WVJBIframe.src = 'https://__bridge_loaded__';
OC 回调
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
NSLog(@"url:%@", url);
}
/*
打印如下:
url:https://__bridge_loaded__
*/
小结
基于上面介绍 OC to JS 和 JS to OC 的原理,我们可以自己封装一个 JS 与 OC 消息分发中心。当然 WebViewJavascriptBridge
已经封装的很好了,也没啥必要重复造轮子,如果业务上只是简单的需要 JS 与 OC 通信,就可以基于上面原理来实现即可。
注意
iframe 的 src 有长度限制,所以 JS 向 OC 发送消息,如果通过 src 来传递,是有限制的。为了避免这种情况,我们可以通过约定一个的 src 告诉 OC,JS 要跟 OC 发送消息了,然后在 webView:decidePolicyForNavigationAction:decisionHandler
收到回调之后,通过 WKWebview
的 evaluateJavaScript:completionHandler
方法来获取 JS 向 OC 要发送的消息数据。
WebViewJavascriptBridge 底层设计
下面就是 WebViewJavascriptBridge
的通信过程。
注意:由于消息的传输都是基于 WKWebView
来实现的,都是在主线程上实现的,所以就不需要考虑多线程问题。

- OC业务层:OC相关业务开发
- OC接口层:
WKWebViewJavascriptBridge
和WebViewJavascriptBridge
分别对应WKWebView
和UIWebView
- 解析层:
WebViewJavascriptBridgeBase
- JS接口层:
WebViewJavascriptBridge_JS
- JS业务层:JS相关业务开发
接下来主要讨论应用 WKWebView
的情况,UIWebView
实现类似。
分析 OC 向 JS 发送消息过程
OC 向 JS 发送消息,我们以下为例子来分析。
// 业务调用OC接口层
[_bridge callHandler:@"testJavascriptHandler" data:data responseCallback:^(id response) {
NSLog(@"testJavascriptHandler responded: %@", response);
}];
// 解析层转换后,通过 WKWebView.evaluateJavaScript 调用
NSString *javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil]
// JS接口层收到响应
function _handleMessageFromObjC(messageJSON) {
//...
}
由于向 JS 发送消息之后要进行 responseCallback
回调,所以需要把 responseCallback
进行缓存起来。考虑到同一个事件有可能同时发送多个消息,所以对每次发送消息需要生成一个 id 进行标记。有了这个 id,当 JS 向 OC 发送消息时,如果消息中包含这个 id,说明这是一个需要进行 responseCallback
回调。
顺便说一下题外话,OC 向 JS 发送消息,虽然是在主线程,但是发送和回调不是同步的,所以需要用 id 对消息进行标记。
下面看一下具体实现,以下为简化之后的逻辑
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionary dictionary];
if (data) {
message[@"data"] = data;
}
if (responseCallback) {
NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
}
if (handlerName) {
message[@"handlerName"] = handlerName;
}
[self _dispatchMessage:message];
}
- (void)_dispatchMessage:(NSMutableDictionary*)message {
NSData *messageData = [NSJSONSerialization dataWithJSONObject:message options:0 error:nil]
NSString *messageJSON = [[NSString alloc] initWithData:messageData encoding:NSUTF8StringEncoding];
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
}
回调其实就是走了一遍 JS 向 OC 发送消息的过程,然后根据消息中是否包含标记 id 来判断。
下面分析 JS 向 OC 发送消息过程
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 = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
可以发现,每次 JS 向 OC 发送消息的时候,都会通过修改一次 iframe 的 src,触发WKWebView
的 WKNavigationDelegate
协议中的函数。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
// 初始化标记
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
// JS 向 OC 发送消息标记
[self WKFlushMessageQueue];
} else {
// 未知消息
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
- (void)WKFlushMessageQueue {
NSString *javaScriptString = @"WebViewJavascriptBridge._fetchQueue();";
[_webView evaluateJavaScript:javaScriptString completionHandler:^(NSString* result, NSError* error) {
// result 就是 js 消息队列数据列表
if (error != nil) {
NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
}
[_base flushMessageQueue:result];
}];
}
// 下面就是拿到 js 的消息队列的分发逻辑(只保留关键逻辑)
- (void)flushMessageQueue:(NSString *)messageQueueString{
id messages = [self _deserializeMessageJSON:messageQueueString];
for (NSMutableDictionary* message in messages) {
NSString* responseId = message[@"responseId"];
if (responseId) {
// OC 向 JS 主动发送消息之后的回调逻辑
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
// JS 向 OC 主动发送消息,OC 监听 JS 消息逻辑
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
// 这里是 JS 发送消息之后的回调逻辑
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
NSMutableDictionary* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _dispatchMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
小结
- OC 向 JS 发送消息:
evaluateJavaScript:completionHandler:
- JS 向 OC 发送消息:
- 第一步:JS 修改 iframe 的 src 触发
WKWebView
的WKNavigationDelegate
协议中的函数 - 第二步:OC 通过
evaluateJavaScript:completionHandler:
获取 JS 向 OC 发送的消息数据
- 第一步:JS 修改 iframe 的 src 触发
网友评论