天天看點

WebViewJavascriptBridge源碼分析

最近抽時間看了一遍WebViewJavascriptBridge這個開源架構,把看到的内容記錄下來

源碼位址:https://github.com/marcuswestin/WebViewJavascriptBridge

1、對外接口

初始化OC

初始化JS

[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview handler:(WVJBHandler)handler]

[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview webViewDelegate:(UIWebViewDelegate*)webViewDelegate handler:(WVJBHandler)handler]

document.addEventListener('WebViewJavascriptBridgeReady', function onBridgeReady(event) { ... }, false)

bridge.init(function messageHandler(data, response) { ... })

OC發送消息to JS

JS發送消息to OC

[bridge send:(id)data]

[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]

bridge.send("Hi there!")

bridge.send({ Foo:"Bar" })

bridge.send(data, function responseCallback(responseData) { ... })

OC注冊事件(先)

JS調用事件(後)

[bridge registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler]

WebViewJavascriptBridge.callHandler("handlerName")

OC調用事件(後)

JS注冊事件(先)

[bridge callHandler:(NSString*)handlerName data:(id)data]

[bridge callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)callback]

bridge.registerHandler("handlerName", function(responseData) { ... })

三類API接口用于OC與JS之間互動:初始化接口;發送消息接口,并且可以添加發送消息完成的回調函數;事件注冊和調用接口,需要先在一端注冊事件,另一端可以根據事件名稱調用函數

除了上述提到的外部方法:還有兩個方法十分重要,JS部分最重要的内部方法: _handleMessageFromObjC;OC部分重要的内部方法:flushMessageQueue

2、類結構圖

WebViewJavascriptBridge源碼分析

WebViewJavascriptBridge目前既支援原有的UIWebView,也支援iOS8+之後新的WKWebView,使用時可以二選其一;

WebViewjavascriptBridgeBase是通用類,用于處理從Native到JS的消息注入,消息隊列處理和分發,JSON資料的序列化和反序列化,LOG輸出;

3、源碼分析

WebViewJavascriptBridge源碼分析

3.1 消息發送JS-》Native

這兩個函數最後都是調用_doSend({ data:data }, responseCallback)

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

}

首先生成callbackId,由不斷加1的唯一需要和時間戳構成,如果有responseCallback函數,使用callbackId作為索引,存入responseCallbacks對象,等到從OC側傳回的資訊中對應的callbackId與目前responseCallbacks中callbackId相同時,調用回調函數responseCallback;sendMessageQueue是個消息數組,每次的新消息放入其中,messagingIframe是iframe對象,當設定src産生一次請求,在OC端的

(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 會攔截請求内容

代碼:

- (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 ([_baseisCorrectProcotocolScheme:url]) {

        if ([_baseisCorrectHost:url]) {

            NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];

            [_base flushMessageQueue:messageQueueString];

        } else {

            [_base logUnkownMessage:url];

        }

        return NO;

    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {

        return [strongDelegate webView:webView shouldStartLoadWithRequest:requestnavigationType:navigationType];

    } else {

        return YES;

    }

重點部分:執行注入_evaluateJavascript,

OC

-(NSString *)webViewJavascriptFetchQueyCommand {

    return @"WebViewJavascriptBridge._fetchQueue();";

JS

function _fetchQueue() {

    var messageQueueString = JSON.stringify(sendMessageQueue)

    sendMessageQueue = []

   return messageQueueString

這個函數從JS的sendMessageQueue消息隊列擷取内容傳回,這個sendMessageQueue是在之前的_doSend函數中傳入的消息内容,也就是NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];這句代碼獲得從JS側拿到的資料内容,然後調用[_baseflushMessageQueue:messageQueueString];對消息分發處理

- (void)flushMessageQueue:(NSString *)messageQueueString{

    id messages = [self_deserializeMessageJSON:messageQueueString];

    if (![messages isKindOfClass:[NSArray class]]) {

        NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messagesclass], messages);

        return;

    for (WVJBMessage* messagein messages) {

        if (![message isKindOfClass:[WVJBMessage class]]) {

            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messageclass], message);

            continue;

        [self _log:@"RCVD" json:message];

        NSString* responseId = message[@"responseId"];

        if (responseId) {

            WVJBResponseCallback responseCallback =_responseCallbacks[responseId];

            responseCallback(message[@"responseData"]);

            [self.responseCallbacksremoveObjectForKey:responseId];

            WVJBResponseCallback responseCallback =NULL;

            NSString* callbackId = message[@"callbackId"];

            if (callbackId) {

                responseCallback = ^(id responseData) {

                    if (responseData == nil) {

                        responseData = [NSNullnull];

                    }

                    WVJBMessage* msg = @{@"responseId":callbackId, @"responseData":responseData };

                    [self _queueMessage:msg];

                };

            } else {

                responseCallback = ^(id ignoreResponseData) {

                    // Do nothing

            }

            WVJBHandler handler;

            if (message[@"handlerName"]) {

                handler = self.messageHandlers[message[@"handlerName"]];

                handler = self.messageHandler;

            if (!handler) {

                [NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];

            handler(message[@"data"], responseCallback);

這個是整個架構中OC側重要的函數,但是目前首先分析消息發送JS-》Native涉及到的部分内容,傳回的消息包含callbackId,資料拼接後調用[self_queueMessage:msg];發送回JS側的資料改為responseId為關鍵字key,具體如下:

- (void)_queueMessage:(WVJBMessage*)message {

    if (self.startupMessageQueue) {

        [self.startupMessageQueueaddObject:message];

        [self _dispatchMessage:message];

self.startupMessageQueue隻有首次啟動時有效,之後為nil,是以都是走[self_dispatchMessage:message];

- (void)_dispatchMessage:(WVJBMessage*)message {

    NSString *messageJSON = [self_serializeMessage:message];

    [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"];

    NSString* javascriptCommand = [NSStringstringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];

    if ([[NSThreadcurrentThread] isMainThread]) {

        [self _evaluateJavascript:javascriptCommand];

        dispatch_sync(dispatch_get_main_queue(), ^{

            [self _evaluateJavascript:javascriptCommand];

        });

此函數對message特殊字元進行轉義處理,然後執行JS注入語句,WebViewJavascriptBridge._handleMessageFromObjC執行到JS側

這個是整個架構中JS側重要的函數,用于處理從OC側傳回的消息

function _dispatchMessageFromObjC(messageJSON) {

        setTimeout(function _timeoutDispatchMessageFromObjC() {

            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({ responseId:callbackResponseId, responseData:responseData })

                    }

                }

                var handler = WebViewJavascriptBridge._messageHandler

                if (message.handlerName) {

                    handler = messageHandlers[message.handlerName]

                try {

                    handler(message.data, responseCallback)

                } catch(exception) {

                    if (typeof console != 'undefined') {

                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)

            }

        })

    }

執行JS側本地回調函數

3.2 消息發送 OC--》JS

調用

    [_base sendData:dataresponseCallback:responseCallback handlerName:nil];

執行 _queueMessage

3.3 OC注冊事件和JS調用

OC側注冊

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {

    _base.messageHandlers[handlerName] = [handlercopy];

JS調用

function callHandler(handlerName, data, responseCallback) {

     _doSend({ handlerName:handlerName, data:data }, responseCallback)

加入handlerName和data資料傳給OC側,JS側記錄responseCallback,最後也會走到- (void)flushMessageQueue:(NSString *)messageQueueString函數中,由于既沒有callbackId也沒有responseId,是以隻處理handlerName及相關資料,最後走到 - (void)flushMessageQueue:(NSString*)messageQueueString解析,OC側執行之前注冊的handler并傳入data資料

3.4 JS注冊事件和OC調用

JS注冊

function registerHandler(handlerName, handler) {

     messageHandlers[handlerName] = handler

本地記錄

OC調用

- (void)callHandler:(NSString *)handlerName data:(id)data {

    [self callHandler:handlerName data:dataresponseCallback:nil];

- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {

    [_base sendData:data responseCallback:responseCallbackhandlerName:handlerName];

handlerName和data  在 - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName中處理

4 、總結

1、與其它架構相比,此架構沒有采用url攔截解析參數方式,而是多次JS注入參數擷取,API接口暴露的操作在底層需要多次OC與JS之間互動完成

2、WKwebview部分沒有使用新的delgate方法,而是沿用iframe方式,個人覺得采用新接口可能效率更高