iOS开发 - WebViewJavascriptBridge分析

强烈推荐: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看看

iOS开发 - WebViewJavascriptBridge分析_第1张图片
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 :是一个字符串,内部数据格式如下:

messageQueueString具体数据
"[{"handlerName":"GetCurrentVersion","data":{},"callbackId":"cb_2_1548142676517"}]"

message[@"data"]: 我们注册时候的参数。
responseCallback:显而易见是我们注册时候的回调函数。

handler(message[@"data"], responseCallback); 运行我们注册的回调函数,会回到我们注册的地方:

注册时候的block代码

运行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;方法是如何被调用的

再次搜索、很明显了、是拦截协议并且判断复合要求之后直接调用的。没什么太绕的东西。

iOS开发 - WebViewJavascriptBridge分析_第2张图片
WKWebView中调用flushMessageQueue
- (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"}]"

但是又如何触发 webView:decidePolicyForNavigationAction:decisionHandler: 这个代理方法呢?

js中调用Native注册的方法

// app.html
 bridge.callHandler('getUserId','参数不需要的话可以省略不谢',function(response){
   log(response.userId)
 })

 // WebViewJavascriptBridge_JS
 function callHandler(handlerName, data, responseCallback) {
      if (arguments.length == 2 && typeof data == 'function') {
          responseCallback = data;
          data = null;
      }
      _doSend({ handlerName:handlerName, data:data }, responseCallback);
 }

进行了一些参数处理(js中很多都会根据传入参数数量的不同、内部进行进一步处理),处理结束直接丢给_doSend函数

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;
}
  • 就是说js的callback函数在这里会被保存起来。以callbackId为键保存在responseCallbacks这个字典中、将来可以根据callbackId获取、完成回调。

  • callbackId也作为新的参数、添加进了message字典中。

  • messagingIframe:
    这个应该比较容易理解。iframe是一个内嵌的网页标签。你既然修改了对应的src(链接)、webView自然会收到一个重定向的请求。

  • sendMessageQueue
    既然修改了iframe的src、让webVIew拦截了协议。sendMessageQueue自然就是为了提供参数而存在的了。

我们来找找看(搜索sendMessageQueue)。
//WebViewJavascriptBridge_JS
function _fetchQueue() {
      var messageQueueString = JSON.stringify(sendMessageQueue);
      sendMessageQueue = [];
      return messageQueueString;
}
//#import "WebViewJavascriptBridgeBase.h"
- (NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}


- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
/*** 省略 ***/
    // js主动调启oc
    [self WKFlushMessageQueue];
/*** 省略 ***/
}


- (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];
    }];
}

这里修改src = wvjbscheme://__WVJB_QUEUE_MESSAGE__,也就是我们后面拦截协议时,拦截到的url,那么参数也就是 webview执行JSWebViewJavascriptBridge._fetchQueue(); 从而获取sendMessageQueue.push(message)传递出来的值。

总结

1、OC端注册,将 方法名 + 回调 存储到 messageHandlers 中;
2、JS发出请求,修改iframe的src,进行重定向,webview触发代理回调webView: decidePolicyForNavigationAction: decisionHandler:进行协议拦截;
3、OC拦截到URL,注意这里的URL并不是将参数、callbackId等直接作为url发送出来,而是wvjbscheme://__WVJB_QUEUE_MESSAGE__,那么参数又是怎么来的呢?
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。




OC调用JS

先看js文件,还是想先从注册看起。

js中的bridge实例初始化。

var _setupWebViewJavascriptBridge = function(callback){   //ios桥函数
    if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
};

js中注册方法的代码

bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
     var responseData = { 'Javascript Says':'Right back atcha!' };
     if(responseCallback) {
         responseCallback(responseData);
     };
 });
// WebViewJavascriptBridge_JS.h
function registerHandler(handlerName, handler) {
    messageHandlers[handlerName] = handler;
}

OC又是如何调用的呢?

[_WKwebViewBridge callHandler:@"testJavascriptHandler" data:@"some message"];

- (void)_dispatchMessage:(WVJBMessage*)message {
/*** 省略 ***/
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];
    } 
/*** 省略 ***/
}

_handleMessageFromObjC又做了啥?

function _handleMessageFromObjC(messageJSON) {
        _dispatchMessageFromObjC(messageJSON);
}

拿到OC发来的messageJSON。里面有responseId/handlerName以及responseData。然后通过responseId将js中对应的callback调起/执行指定已经注册函数。

看来和js调用oc的方式基本相同。

你可能感兴趣的:(iOS开发 - WebViewJavascriptBridge分析)