程序员iOS学习笔记WebView

WebJavaScriptBridge源码解读

2018-11-23  本文已影响17人  半城coding

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

//通过改方法oc代码向webView注册js方法
   - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
//通过该方法,拦截js回调oc的请求
   - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//通过改方法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、技术点:

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

6、执行流程图:


129508BD539FAC2E3E66AE5CA3C64B38.jpg

7、各层职责

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

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)是有工厂化方法的意思,但是却埋了一个坑。

上一篇 下一篇

猜你喜欢

热点阅读