美文网首页
iOS WebViewJavascriptBridge 源码解析

iOS WebViewJavascriptBridge 源码解析

作者: forping | 来源:发表于2021-03-15 14:53 被阅读0次

    当我们使用UIWebView来显示html页面时,和js交互的代码

    // 创建bridge对象
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:_wkWebView];
    [self.bridge setWebViewDelegate:self];
    // 调用js
    [self.bridge callHandler:"" data:nil responseCallback:^(id responseData) {
    }];
    // 监听js调用
    [self.bridge registerHandler:"" handler:^(id data, WVJBResponseCallback responseCallback) {
            responseCallback(@"");
    }];
    

    那么底层怎么实现的呢?

    创建bridge

    + (instancetype)bridgeForWebView:(id)webView {
        return [self bridge:webView];
    }
    + (instancetype)bridge:(id)webView {
    #if defined supportsWKWebView  // 适配WKWebView
        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;
    }
    

    调用

    - (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
        [_base sendData:data responseCallback:responseCallback handlerName:handlerName];
    }
    

    监听

    typedef void (^WVJBResponseCallback)(id responseData);
    typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
    
    - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
        _base.messageHandlers[handlerName] = [handler copy];
    }
    

    以上的代码是 UIWebView和WKWebView 通用的,不一样的是提供的和 js 交互的代码

    UIWebView

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
        if (webView != _webView) { return YES; }
        
        NSURL *url = [request URL];
        __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
        if ([_base isWebViewJavascriptBridgeURL:url]) { // 通过scheme和protocol判断是不是自己定义的url
            if ([_base isBridgeLoadedURL:url]) {
                [_base injectJavascriptFile]; // 注入js语句
            } else if ([_base isQueueMessageURL:url]) {// 是请求数据的url
                NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]; // 当html触发事件后,会执行webViewJavascriptFetchQueyCommand方法对应的js语句,获取当前待处理的事件
    // 刷新数据
                [_base flushMessageQueue:messageQueueString];
            } else {
                [_base logUnkownMessage:url];
            }
            return NO;
        } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
    // 回调给webView的协议
            return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
        } else {
            return YES;
        }
    }
    

    WKWebView

    - (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;
    
        if ([_base isWebViewJavascriptBridgeURL:url]) {
            if ([_base isBridgeLoadedURL:url]) {
                [_base injectJavascriptFile];
            } else if ([_base isQueueMessageURL:url]) {
                [self WKFlushMessageQueue];
            } else {
                [_base logUnkownMessage:url];
            }
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
        
        if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
            [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
        } else {
            decisionHandler(WKNavigationActionPolicyAllow);
        }
    }
    
    - (void)WKFlushMessageQueue {
        [_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];
        }];
    }
    

    可以看到,一直在使用 WebViewJavascriptBridgeBase *_base;

    WebViewJavascriptBridgeBase

    @interface WebViewJavascriptBridgeBase : NSObject
    @property (weak, nonatomic) id <WebViewJavascriptBridgeBaseDelegate> delegate; // bridge对象
    @property (strong, nonatomic) NSMutableArray* startupMessageQueue;
    @property (strong, nonatomic) NSMutableDictionary* responseCallbacks; // 调用后,回调的block数组
    @property (strong, nonatomic) NSMutableDictionary* messageHandlers; // 监听的字典,key是监听的名字,value是回调block
    @property (strong, nonatomic) WVJBHandler messageHandler; // 
    
    + (void)enableLogging;
    + (void)setLogMaxLength:(int)length;
    - (void)reset;
    - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName;
    - (void)flushMessageQueue:(NSString *)messageQueueString;
    - (void)injectJavascriptFile;
    - (BOOL)isWebViewJavascriptBridgeURL:(NSURL*)url;
    - (BOOL)isQueueMessageURL:(NSURL*)urll;
    - (BOOL)isBridgeLoadedURL:(NSURL*)urll;
    - (void)logUnkownMessage:(NSURL*)url;
    - (NSString *)webViewJavascriptCheckCommand;
    - (NSString *)webViewJavascriptFetchQueyCommand;
    - (void)disableJavscriptAlertBoxSafetyTimeout;
    
    @end
    

    发送数据

    - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
        NSMutableDictionary* message = [NSMutableDictionary dictionary];
        if (data) {
            message[@"data"] = data;
        }
        
        if (responseCallback) { // 标识id
            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)_queueMessage:(WVJBMessage*)message {
        if (self.startupMessageQueue) {// 如果还没有注入js,就先保存起来
            [self.startupMessageQueue addObject:message];
        } else {
            [self _dispatchMessage:message];
        }
    }
    

    直接派发

    - (void)_dispatchMessage:(WVJBMessage*)message {
        NSString *messageJSON = [self _serializeMessage:message pretty:NO]; // 转成json字符串
        [self _log:@"SEND" json:messageJSON];// 输出日志
    // 转义
        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"];
        
    // 调用 WebViewJavascriptBridge的_handleMessageFromObjC方法
        NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
        if ([[NSThread currentThread] isMainThread]) {
            [self _evaluateJavascript:javascriptCommand];
    
        } else {
            dispatch_sync(dispatch_get_main_queue(), ^{
                [self _evaluateJavascript:javascriptCommand];
            });
        }
    }
    
    - (void) _evaluateJavascript:(NSString *)javascriptCommand {
        [self.delegate _evaluateJavascript:javascriptCommand];
    }
    

    监听调用

    - (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"]; // 是否有响应id,如果有响应id,  证明是oc调用js后,js返回的回调,则检索 responseCallbacks
            if (responseId) {
                WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
                responseCallback(message[@"responseData"]); // 调用回调
                [self.responseCallbacks removeObjectForKey:responseId];
            } else { // 没有响应id,则证明是 js 调用 oc
                WVJBResponseCallback responseCallback = NULL;
                NSString* callbackId = message[@"callbackId"]; // 获取callbackId, 用作oc回调给js
                if (callbackId) {
                    // 创建一个回调的block,当oc调用block的时候, 发送信息给js, 消息id 就是 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
                    };
                }
                
                // 获取监听的block
                WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
                
                if (!handler) {
                    NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                    continue;
                }
                // 调用监听的block
                handler(message[@"data"], responseCallback);
            }
        }
    }
    

    注入js

    嵌入的iframe 需要注入 js语句,来初始化WebViewJavascriptBridge供oc和html交互

    - (void)injectJavascriptFile {
        NSString *js = WebViewJavascriptBridge_js();
        [self _evaluateJavascript:js];
        if (self.startupMessageQueue) {
            NSArray* queue = self.startupMessageQueue;
            self.startupMessageQueue = nil;
            for (id queuedMessage in queue) {
                [self _dispatchMessage:queuedMessage];
            }
        }
    }
    

    html中的代码

    以下是框架中的示例代码

    function setupWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } // 如果存在WebViewJavascriptBridge,证明环境已经初始化好了,直接调用callBack
            if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } // 如果存在WVJBCallbacks,但环境没初始化好,添加到数组里
            window.WVJBCallbacks = [callback];
            var WVJBIframe = document.createElement('iframe');
            WVJBIframe.style.display = 'none';
            WVJBIframe.src = 'https://__bridge_loaded__';
            document.documentElement.appendChild(WVJBIframe);
            setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
        }
    
        setupWebViewJavascriptBridge(function(bridge) {
            var uniqueId = 1
            function log(message, data) {
                var log = document.getElementById('log')
                var el = document.createElement('div')
                el.className = 'logLine'
                el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
                if (log.children.length) { log.insertBefore(el, log.children[0]) }
                else { log.appendChild(el) }
            }
    
            bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
                log('ObjC called testJavascriptHandler with', data)
                var responseData = { 'Javascript Says':'Right back atcha!' }
                log('JS responding with', responseData)
                responseCallback(responseData)
            })
    
            document.body.appendChild(document.createElement('br'))
    
            var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
            callbackButton.innerHTML = 'Fire testObjcCallback'
            callbackButton.onclick = function(e) {
                e.preventDefault()
                log('JS calling handler "testObjcCallback"')
                bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
                    log('JS got response', response)
                })
            }
        })
    

    可以看出来,在调用 setupWebViewJavascriptBridge 方法的时候, 传进去一个 callback,保存到了window.WVJBCallbackscallback 保存了js监听和调用的方法

    之后 页面加载了一个iframe,url是 https://__bridge_loaded__ oc会在加载这个url的时候,往html页面注入下面的 js 语句

    NSString * WebViewJavascriptBridge_js() {
        #define __wvjb_js_func__(x) #x
        
        // BEGIN preprocessorJSCode
        static NSString * preprocessorJSCode = @__wvjb_js_func__(
    ;(function() {
        if (window.WebViewJavascriptBridge) { // 如果初始化完成了,直接返回
            return;
        }
    
        if (!window.onerror) { // 创建error方法
            window.onerror = function(msg, url, line) {
                console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
            }
        }
        window.WebViewJavascriptBridge = { // 创建 WebViewJavascriptBridge
            registerHandler: registerHandler,
            callHandler: callHandler,
            disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
            _fetchQueue: _fetchQueue,
            _handleMessageFromObjC: _handleMessageFromObjC // 原生调用这个方法
        };
    
        var messagingIframe;
        var sendMessageQueue = [];
        var messageHandlers = {};
        
        var CUSTOM_PROTOCOL_SCHEME = 'https';
        var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
        
        var responseCallbacks = {};
        var uniqueId = 1;
        var dispatchMessagesWithTimeoutSafety = true;
    
        function registerHandler(handlerName, handler) {
            messageHandlers[handlerName] = handler;
        }
        
        function callHandler(handlerName, data, responseCallback) {
            if (arguments.length == 2 && typeof data == 'function') { // 参数的适配
                responseCallback = data;
                data = null;
            }
            _doSend({ handlerName:handlerName, data:data }, responseCallback);
        }
        function disableJavscriptAlertBoxSafetyTimeout() {
            dispatchMessagesWithTimeoutSafety = false;
        }
        
        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; // 更改src触发oc的协议方法
        }
    
        function _fetchQueue() {// oc调用 return原生待处理的事件
            var messageQueueString = JSON.stringify(sendMessageQueue);
            sendMessageQueue = [];
            return messageQueueString;
        }
    
        function _dispatchMessageFromObjC(messageJSON) {
            if (dispatchMessagesWithTimeoutSafety) {
                setTimeout(_doDispatchMessageFromObjC);
            } else {
                 _doDispatchMessageFromObjC();
            }
            
            function _doDispatchMessageFromObjC() {
                var message = JSON.parse(messageJSON);
                var messageHandler;
                var responseCallback;
    
                if (message.responseId) {
                    responseCallback = responseCallbacks[message.responseId];
                    if (!responseCallback) {
                        return;
                    }
                    responseCallback(message.responseData);
                    delete responseCallbacks[message.responseId];
                } else {
                    if (message.callbackId) {
                        var callbackResponseId = message.callbackId;
                        responseCallback = function(responseData) {
                            _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                        };
                    }
                    
                    var handler = messageHandlers[message.handlerName];
                    if (!handler) {
                        console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                    } else {
                        handler(message.data, responseCallback);
                    }
                }
            }
        }
        
        function _handleMessageFromObjC(messageJSON) { // 让oc调用的方法
            _dispatchMessageFromObjC(messageJSON);
        }
    
        messagingIframe = document.createElement('iframe');
        messagingIframe.style.display = 'none';
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
        document.documentElement.appendChild(messagingIframe);
    
        registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
        
        setTimeout(_callWVJBCallbacks, 0);
        function _callWVJBCallbacks() { // 调用之前保存的callback
            var callbacks = window.WVJBCallbacks;
            delete window.WVJBCallbacks;
            for (var i=0; i<callbacks.length; i++) {
                callbacks[i](WebViewJavascriptBridge);
            }
        }
    })();
        ); // END preprocessorJSCode
    
        #undef __wvjb_js_func__
        return preprocessorJSCode;
    };
    

    通过看注入的js语句可以知道,基本上和原生的逻辑一致

    相关文章

      网友评论

          本文标题:iOS WebViewJavascriptBridge 源码解析

          本文链接:https://www.haomeiwen.com/subject/jjqkcltx.html