WebViewJavascriptBridge源碼分析

最近抽時間看了一遍WebViewJavascriptBridge這個開源框架,把看到的內(nèi)容記錄下來
源碼地址: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發(fā)送消息to JS
JS發(fā)送消息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調(diào)用事件(后)

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

WebViewJavascriptBridge.callHandler("handlerName")

OC調(diào)用事件(后)
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之間交互:初始化接口;發(fā)送消息接口,并且可以添加發(fā)送消息完成的回調(diào)函數(shù);事件注冊和調(diào)用接口,需要先在一端注冊事件,另一端可以根據(jù)事件名稱調(diào)用函數(shù)

除了上述提到的外部方法:還有兩個方法十分重要,**JS部分最重要的內(nèi)部方法:**_handleMessageFromObjC;OC部分重要的內(nèi)部方法:**flushMessageQueue******
2、類結(jié)構(gòu)圖


WebViewJavascriptBridge目前既支持原有的UIWebView,也支持iOS8+之后新的WKWebView,使用時可以二選其一;
WebViewjavascriptBridgeBase是通用類,用于處理從Native到JS的消息注入,消息隊列處理和分發(fā),JSON數(shù)據(jù)的序列化和反序列化,LOG輸出;

3、源碼分析

![](http://upload-images.jianshu.io/upload_images/1708245-fccb0efdbf86069e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

3.1 消息發(fā)送JS-》Native


**[bridge send:(id)data]**

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

這兩個函數(shù)最后都是調(diào)用_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的唯一需要和時間戳構(gòu)成,如果有responseCallback函數(shù),使用callbackId作為索引,存入responseCallbacks對象,等到從OC側(cè)返回的信息中對應的callbackId與當前responseCallbacks中callbackId相同時,調(diào)用回調(diào)函數(shù)responseCallback;sendMessageQueue是個消息數(shù)組,每次的新消息放入其中,messagingIframe是iframe對象,當設(shè)置src產(chǎn)生一次請求,在OC端的

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 會攔截請求內(nèi)容

代碼:

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

重點部分:執(zhí)行注入_evaluateJavascript,
OC

-(NSString *)webViewJavascriptFetchQueyCommand {    return @"WebViewJavascriptBridge._fetchQueue();";
}
JS
function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue)
    sendMessageQueue = []
   return messageQueueString
}

這個函數(shù)從JS的sendMessageQueue消息隊列獲取內(nèi)容返回,這個sendMessageQueue是在之前的_doSend函數(shù)中傳入的消息內(nèi)容,也就是NSString*messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];這句代碼獲得從JS側(cè)拿到的數(shù)據(jù)內(nèi)容,然后調(diào)用[_baseflushMessageQueue:messageQueueString];對消息分發(fā)處理

- (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];        } else {            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"]];            } else {                handler = self.messageHandler;            }                       if (!handler) {                [NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];            }                       handler(message[@"data"], responseCallback);        }    }
}

這個是整個框架中OC側(cè)重要的函數(shù),但是目前首先分析消息發(fā)送JS-》Native涉及到的部分內(nèi)容,返回的消息包含callbackId,數(shù)據(jù)拼接后調(diào)用[self_queueMessage:msg];發(fā)送回JS側(cè)的數(shù)據(jù)改為responseId為關(guān)鍵字key,具體如下:

- (void)_queueMessage:(WVJBMessage*)message {    if (self.startupMessageQueue) {        [self.startupMessageQueueaddObject:message];    } else {        [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];    } else {        dispatch_sync(dispatch_get_main_queue(), ^{            [self _evaluateJavascript:javascriptCommand];        });    }
}

此函數(shù)對message特殊字符進行轉(zhuǎn)義處理,然后執(zhí)行JS注入語句,WebViewJavascriptBridge._handleMessageFromObjC執(zhí)行到JS側(cè)

這個是整個框架中JS側(cè)重要的函數(shù),用于處理從OC側(cè)返回的消息

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)                    }                }            }        })    }

執(zhí)行JS側(cè)本地回調(diào)函數(shù)

3.2 消息發(fā)送 OC--》JS

**bridge.send("Hi there!")**
**bridge.send({ Foo:"Bar" })**

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

調(diào)用
[_base sendData:dataresponseCallback:responseCallback handlerName:nil];

執(zhí)行 _queueMessage

3.3 OC注冊事件和JS調(diào)用

OC側(cè)注冊

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handlercopy];
}

JS調(diào)用

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

加入handlerName和data數(shù)據(jù)傳給OC側(cè),JS側(cè)記錄responseCallback,最后也會走到- (void)flushMessageQueue:(NSString )messageQueueString函數(shù)中,由于既沒有callbackId也沒有responseId,所以只處理handlerName及相關(guān)數(shù)據(jù),最后走到 - (void)flushMessageQueue:(NSString)messageQueueString解析,OC側(cè)執(zhí)行之前注冊的handler并傳入data數(shù)據(jù)

3.4 JS注冊事件和OC調(diào)用


JS注冊

function registerHandler(handlerName, handler) {
     messageHandlers[handlerName] = handler
}

本地記錄

OC調(diào)用

- (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 、總結(jié)**
****
1、與其它框架相比,此框架沒有采用url攔截解析參數(shù)方式,而是多次JS注入?yún)?shù)獲取,API接口暴露的操作在底層需要多次OC與JS之間交互完成
2、WKwebview部分沒有使用新的delgate方法,而是沿用iframe方式,個人覺得采用新接口可能效率更高
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容