iOS开发 - WebViewJavascriptBridge分析(一)JS调用OC

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

1859399-6ba9dce50dd111d1.png
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 :是一个字符串,内部数据格式如下:

1859399-826e8c8d19e240af.png
messageQueueString具体数据
"[{"handlerName":"GetCurrentVersion","data":{},"callbackId":"cb_2_1548142676517"}]"

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

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

1859399-b5b9135db60685b5.png
注册时候的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;方法是如何被调用的

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

1859399-d9cb717c6d4432af.png
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"}]"

总结

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。

猜你喜欢

转载自blog.csdn.net/weixin_33695450/article/details/86878534
今日推荐