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目前既支持原有的UIWebView,也支持iOS8+之后新的WKWebView,使用時可以二選其一;
WebViewjavascriptBridgeBase是通用類,用于處理從Native到JS的消息注入,消息隊列處理和分發,JSON數據的序列化和反序列化,LOG輸出;

3、源碼分析

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

3.1 消息發送JS-》Native


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

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

這兩個函數最后都是調用_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];        } 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側重要的函數,但是目前首先分析消息發送JS-》Native涉及到的部分內容,返回的消息包含callbackId,數據拼接后調用[self_queueMessage:msg];發送回JS側的數據改為responseId為關鍵字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];        });    }
}

此函數對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

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

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

調用
[_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方式,個人覺得采用新接口可能效率更高
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容