WebJavaScriptBridge源码解读

原文地址 :https://lm1024.xyz/archives/59
1、这个库解决的问题
以一种优雅的方式,解决OC与UIWebView(WKWebView)上js交互问题
2、实现原理的核心方法 :

  • UIWebView
//通过改方法oc代码向webView注册js方法
   - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
//通过该方法,拦截js回调oc的请求
   - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
  • WKWebView
//通过改方法oc代码向webView注册js方法
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
//通过该方法,拦截js回调oc的请求
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

3、约定的协议:
kNewProtocolScheme @"https"
kQueueHasMessage @"wvjb_queue_message"
kBridgeLoaded @"bridge_loaded"
webview会向oc发送上述三种类型的request请求。 oc端扑捉到对应的请求后通过协议类型来使用不同的方式处理。

4、技术点:

  • oc向js注册一个方法后,js调用oc的方法怎么把处理好的数据回调给js。(直白点就是:对方调用了我的方法,我处理完数据之后。怎么把我处理后的数据回调给对方)
  • js和oc对象的数据格式互换。

5、阅读释义:此处以oc的角度来释义

  • handlerCallBack: 指的是js代码调用oc向js注册的方法时,oc端执行的回调。
  • resposeCallBack:
    1)resposeCallBack 作为参数包含在 handlerCallBack中的时候,指的是js调用oc方法,oc端处理数据完毕后,将数据传递给js端的回调。
    2)resposeCallBack 作为- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback方法的参数时候,指的是oc调用js代码后,js处理完数据给oc的回调。

6、执行流程图:


WebJavaScriptBridge源码解读_第1张图片
129508BD539FAC2E3E66AE5CA3C64B38.jpg

7、各层职责

  • WebViewJavascriptBridge, WKWebViewJavascriptBridge:负责oc端和js端的交互。将oc端的生成的消息桥接给js端,同时也负责捕获js端发送给oc端的消息。
  • WebViewJavascriptBridgeBase:消息处理分发中心。WebViewJavascriptBridge和WKWebViewJavascriptBridge 接受到oc的发送消息的命令后,生成对应的js消息格式返回给WebViewJavascriptBridge和WKWebViewJavascriptBridge注册至js端。 同时解析WebViewJavascriptBridge和WKWebViewJavascriptBridge捕获js的消息后解析消息然后回调oc端对应的方法。

8、核心代码:

- (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 _queueMessage:message];
}
  • 该方法oc调用js的方法时负责生成对应的消息格式,然后分发给WebViewJavascriptBridge或者WKWebViewJavascriptBridge执行对应的js代码
- (void)flushMessageQueue:(NSString *)messageQueueString{
    if (messageQueueString == nil || messageQueueString.length == 0) {
        NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
        return;
    }

    id messages = [self _deserializeMessageJSON:messageQueueString];
    for (WVJBMessage* message in messages) {
        if (![message isKindOfClass:[WVJBMessage class]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
            continue;
        }
        [self _log:@"RCVD" json:message];
        
        NSString* responseId = message[@"responseId"];
        if (responseId) {
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            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
                };
            }
            
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            
            handler(message[@"data"], responseCallback);
        }
    }
}
  • 这段代码在WebViewJavascriptBridge或者WKWebViewJavascriptBridge捕捉到js调用oc代码的消息后解析消息然后找到对应的oc回调block,回调给oc。

9、巧妙点:
1)、在阅读之前我是非常好奇,在这个库里面是怎么处理oc的responseCallBack和js回调callBackFunction 是怎么相互转换的。阅读之后确实感觉作者这样处理的方式真的十分巧妙。作者在oc 和 js端都维护了responseCallbacks和messageHandlers两个字典,每次生成message都会给该message生成一个id,这样两边通信的时候就能通过message id来获取各自平台的 对应的回调方法。这样就直接把oc端和js端给隔离开了,两端各自干各自的事情互不关心。

10、小瑕疵:
在WebViewJavascriptBridge中的下面代码中

+ (instancetype)bridge:(id)webView {
#if defined supportsWKWebView

   if ([webView isKindOfClass:[WKWebView class]]) {
       return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
   }
#endif
   if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) {
       WebViewJavascriptBridge* bridge = [[self alloc] init];
       [bridge _platformSpecificSetup:webView];
       return bridge;
   }
   [NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
   return nil;
}

看到这么一段代码

    if ([webView isKindOfClass:[WKWebView class]]) {
       return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
   }

瞬间有一种跳戏的感觉,初始化的一个B然而却用A的引用指向B。虽然理解作者的意图是想兼容WKWebView。但是这样写可能会给上层业务层埋下一个坑,例如:使用者在不知情的情况下用categroy给WebViewJavascriptBridge新加了一个方法,使用者最初使用的UIWebView,那么一点问题也没有。后来迁移到wkWebView后,调用category里面的方法就会闪退。

11、个人觉得改进点:
1、WKWebViewJavascriptBridge和WebViewJavascriptBridge的.h文件里面的内容几乎一模一样,是否可以抽成一个基类或者接口,这样上层使用的时候就不必要关系我用的是WKWebViewJavascriptBridge还是WebViewJavascriptBridge创建的对象。
2、既然要根据webView的类型来初始化不同bridge对象,那么抽出一个工厂方法是不是好一点。虽然+ (instancetype)bridge:(id)webView)是有工厂化方法的意思,但是却埋了一个坑。

你可能感兴趣的:(WebJavaScriptBridge源码解读)