最近抽時間看了一遍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、源碼分析

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方式,個人覺得采用新接口可能效率更高