强烈推荐:iOS源码补完计划-WebViewJavascriptBridge实现原理
iOS下JS与OC互相调用(六)--WKWebView + WebViewJavascriptBridge
JS调用OC
//将版本信息发送给Html
[_WKwebViewBridge registerHandler:@"GetCurrentVersion" handler:^(id data, WVJBResponseCallback responseCallback) {
// 获取当前版本号
NSString *appVersion = @"1.0.0";
// 反馈给JS
responseCallback(appVersion);
}];
#import "WebViewJavascriptBridge.h"
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
- _base
@implementation WKWebViewJavascriptBridge {
WebViewJavascriptBridgeBase *_base;
}
WebViewJavascriptBridge所持有的WebViewJavascriptBridgeBase(简称base)对象。 - messageHandlers
@property (strong, nonatomic) NSMutableDictionary* messageHandlers;
字典。存储了注册的方法名、ballback。
就是在注册的时候将方法名、block。存储起来备用。
然后、线索断了。也就是说、ios这边主动做的事情、已经没了。
既然存储起来了,那又在什么地方使用了呢?我们搜索messageHandlers
看看
进一步,我们查看messageHandlers调用的地方 - (void)flushMessageQueue:(NSString *)messageQueueString
- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
// 省略 ..........
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
messageQueueString :是一个字符串,内部数据格式如下:
"[{"handlerName":"GetCurrentVersion","data":{},"callbackId":"cb_2_1548142676517"}]"
message[@"data"]: 我们注册时候的参数。
responseCallback:显而易见是我们注册时候的回调函数。
当handler(message[@"data"], responseCallback);
运行我们注册的回调函数,会回到我们注册的地方:
运行OC代码,向JS反馈信息调用responseCallback(appVersion)
,又回到了- (void)flushMessageQueue:(NSString *)messageQueueString
中
- (void)flushMessageQueue:(NSString *)messageQueueString{
// 省略 ..............
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
// 省略 ..............
}
发现我们传递过来的appVersion 就是 responseData 的值,并且重新整理成一个WVJBMessage(也就是NSDictionary)对象。
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[self _log:@"SEND" json:messageJSON];
// 对json字符串进行一系列格式化处理
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
message 的值
{
responseData = "1.1.3";
responseId = "cb_2_1548206601903";
}
javascriptCommand 的值
WebViewJavascriptBridge._handleMessageFromObjC('{\"responseId\":\"cb_2_1548206601903\",\"responseData\":\"1.1.3\"}');
将获取的数据整理成一个WVJBMessage(也就是NSDictionary)对象后,调用_evaluateJavascript:方法,底层是让webview去注入这段js函数
至于_handleMessageFromObjC的实现,就是属于WebViewJavascriptBridge_js文件中的范畴了。一会从js端切入的时候再去看。
再回过头来看看-(void)flushMessageQueue:(NSString *)messageQueueString;方法是如何被调用的
再次搜索、很明显了、是拦截协议并且判断复合要求之后直接调用的。没什么太绕的东西。
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
// js通过Bridge发起的url
if ([_base isCorrectProcotocolScheme:url]) {
if ([_base isBridgeLoadedURL:url]) {
// 注入js(WebViewJavascriptBridge_js)
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
// js主动调启oc,也就是我们上面分析的步骤
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
// 拦截
decisionHandler(WKNavigationActionPolicyCancel);
}
else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
[_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
} else {
// 不拦截,正常回调给webView的VC
decisionHandler(WKNavigationActionPolicyAllow);
}
}
- (void)WKFlushMessageQueue {
// webView执行JS `WebViewJavascriptBridge._fetchQueue();`
[_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];
}];
}
WKWebView执行JSWebViewJavascriptBridge._fetchQueue();
,得到result
"[{"handlerName":"GetCurrentVersion","data":{},"callbackId":"cb_2_1548208471860"}]"
总结
1、OC端注册,将 方法名 + 回调 存储到
messageHandlers
中;
2、JS发出请求,OC的webview通过代理回调webView: decidePolicyForNavigationAction: decisionHandler:
进行协议拦截;
3、OC拦截到URL,注意这里的URL并不是将参数、callbackId等直接作为url发送出来,而是wvjbscheme://__WVJB_QUEUE_MESSAGE__
,那么参数又是怎么来的呢?参数通过bridgejs生成、并且获取。具体这一步如何实现、下面分析js中调用Native的时候再来看;
4、wkwebview执行JS代码WebViewJavascriptBridge._fetchQueue();
从而获得了具体的参数:"[{"handlerName":"GetCurrentVersion","data":{},"callbackId":"cb_2_1548208471860"}]"
;
5、拿到这些参数后,将存储在messageHandlers
中的block执行,也就是执行注册时的回掉了(这里可以执行OC相关代码,我们这里是获取版本号)。
6、当拿到版本号后,需要反馈给JS,将拿到的数据整理成新的数据@{ @"responseId":callbackId, @"responseData":responseData };
,调用bridgejs文件中的_handleMessageFromObjC
方法。将返回值callback给js中的指定callback。