WebViewJavaScriptBridge是被使用最多狸演,也是最好用的移動(dòng)端與JS交互的第三方框架项阴,在此總結(jié)一下它的使用方法和內(nèi)部原理滑黔。
一.使用方法
iOS端(這里以UIWebView為例)
1.導(dǎo)入頭文件
#import "WebViewJavascriptBridge.h"
2.聲明橋接對(duì)象
@property WebViewJavascriptBridge* bridge;
3.初始化橋接對(duì)象
self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
4.注冊(cè)函數(shù)
[self.bridge registerHandler:@"getUserInfo" handler:^(id data, WVJBResponseCallback responseCallback) {
NSDictionary *dict = @{
@"userid":@"0001"
};
responseCallback(dict);
}];
@"getUserInfo":與JS端共同商定的函數(shù)名,JS在調(diào)用時(shí)函數(shù)名必須一致
data:函數(shù)調(diào)用時(shí)傳遞過來的數(shù)據(jù)
responseCallback:函數(shù)被調(diào)用時(shí)傳遞過來的回調(diào)函數(shù)
4.調(diào)用函數(shù)
[self.javaScriptBridge callHandler:@"webViewRefresh" data:nil responseCallback:^(id responseData) {
}];
@"webViewRefresh":與JS端共同商定的函數(shù)名环揽,JS在注冊(cè)時(shí)函數(shù)名必須一致
data:調(diào)用函數(shù)時(shí)傳遞過去的數(shù)據(jù)
responseCallback:調(diào)用函數(shù)時(shí)傳遞過去的回調(diào)函數(shù)
responseData:傳遞過去的回調(diào)函數(shù)被JS調(diào)用時(shí)所傳遞過來的數(shù)據(jù)
JS端
1.聲明一個(gè)函數(shù)略荡,將此函數(shù)復(fù)制到JS文件中
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
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)
}
2.注冊(cè)或者調(diào)用交互方法
setupWebViewJavascriptBridge(function(bridge) {
/* Initialize your app here */
bridge.registerHandler('JS Echo', function(data, responseCallback) {
console.log("JS Echo called with:", data)
responseCallback(data)
})
bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
console.log("JS received response:", responseData)
})
})
二.內(nèi)部原理
1.框架文件
① WebViewJavascriptBridgeBase中聲明和實(shí)現(xiàn)了基礎(chǔ)的方法和屬性
② WebViewJavascriptBridge_js中是一段JS代碼,在初始化時(shí)編譯歉胶,用于交互
③ WebViewJavascriptBridge和WKWebViewJavascriptBridge是分別適應(yīng)UIWebView和WKWebView的繼承于WebViewJavascriptBridgeBase的子類
2.初始化
在JS文件被加載時(shí)會(huì)執(zhí)行setupWebViewJavascriptBridge的方法汛兜,改變iframe.src,就會(huì)發(fā)起頁面請(qǐng)求跨扮,從而被UIWebView的代理方法攔截到序无,執(zhí)行injectJavascriptFile验毡,編譯WebViewJavascriptBridge_js衡创,為后面的交互做好準(zhǔn)備帝嗡,這就是初始化的過程。
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
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)
}
3.JS調(diào)用OC函數(shù)原理(這里以UIWebView為例)
首先需要OC注冊(cè)函數(shù)
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"Response from testObjcCallback");
}];
OC注冊(cè)函數(shù)需要調(diào)用WebViewJavascriptBridge的方法
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
那么看一下它的做了什么
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
它將handlerName為key璃氢,以handler為value儲(chǔ)存在一個(gè)叫做messageHandlers的字典中哟玷,注冊(cè)就完成了
然后需要JS調(diào)用OC注冊(cè)好的函數(shù)
bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
log('JS got response', response)
})
JS調(diào)用函數(shù)需要用到WebViewJavascriptBridge_js的方法
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, 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;
}
這個(gè)方法將handlerName, data, responseCallback儲(chǔ)存在message字典中,并將message字典儲(chǔ)存在sendMessageQueue數(shù)組中一也,最后改變了iframe.src巢寡,發(fā)送請(qǐng)求
- (NSString *)webViewJavascriptFetchQueyCommand {
return @"WebViewJavascriptBridge._fetchQueue();";
}
被被UIWebView的代理方法攔截到之后首先執(zhí)行JS方法WebViewJavascriptBridge._fetchQueue(),拿到要調(diào)用的函數(shù)的信息
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
然后執(zhí)行OC的flushMessageQueue方法
- (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"];
if (responseId) {//當(dāng)OC調(diào)用JS注冊(cè)的函數(shù)后執(zhí)行回調(diào)方法時(shí)才有responseId椰苟,此處后面會(huì)介紹
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {//當(dāng)JS調(diào)用OC注冊(cè)的函數(shù)
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {//如果有JS的回調(diào)
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
//注意此處添加了responseId抑月,之后在WebViewJavascriptBridge_js的_dispatchMessageFromObjC方法中會(huì)用到
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];//執(zhí)行JS回調(diào)函數(shù)
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];//根據(jù)函數(shù)名拿到handler
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);//執(zhí)行handler
}
}
}
下面我們?cè)倏匆幌翵S的回調(diào)函數(shù)是如何被執(zhí)行的
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[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"];
//執(zhí)行JS的_handleMessageFromObjC拿到要執(zhí)行的回調(diào)函數(shù)
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
//執(zhí)行回調(diào)函數(shù)
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
最后再看一下JS的handleMessageFromObjC方法是如何找到相應(yīng)的回調(diào)函數(shù)的
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
if (message.responseId) {
//之前OC的flushMessageQueue方法中已經(jīng)添加了responseId,那么進(jìn)到這個(gè)判斷,根據(jù)responseId找到回調(diào)函數(shù)舆蝴,然后執(zhí)行
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);
}
}
}
}
至此谦絮,JS調(diào)用OC注冊(cè)的函數(shù)流程完結(jié),難點(diǎn)在于responseCallback如何執(zhí)行洁仗,要記住responseCallback是由調(diào)用方傳入的參數(shù)层皱,應(yīng)由調(diào)用方執(zhí)行。
4.OC調(diào)用JS函數(shù)原理(這里以UIWebView為例)
首先需要JS注冊(cè)函數(shù)
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)
})
看一下這個(gè)方法的實(shí)現(xiàn)赠潦,在WebViewJavascriptBridge_js中
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
可以看到這里和OC注冊(cè)函數(shù)時(shí)的操作一樣叫胖,都是將handlerName為key,以handler為value儲(chǔ)存在一個(gè)叫做messageHandlers的字典中她奥,注冊(cè)就完成了
然后就是OC開始調(diào)用這個(gè)函數(shù)
[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
看一下這個(gè)方法的實(shí)現(xiàn)瓮增,在WebViewJavascriptBridge中
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionary dictionary];
if (data) {
message[@"data"] = data;
}
if (responseCallback) {//注意這里,如果有responseCallback哩俭,就會(huì)callbackId為key存起來
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) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[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 = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
可見最終還是執(zhí)行到WebViewJavascriptBridge_js中的handleMessageFromObjC方法才能拿到要執(zhí)行的JS代碼钉赁,那我們看一下這里跟JS調(diào)用OC函數(shù)不同在哪里
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 {//注意這次會(huì)進(jìn)入下面的判斷
if (message.callbackId) {//如果有responseCallback,注意之前的處理
var callbackResponseId = message.callbackId;
//注意此處它為responseCallback賦值了一個(gè)函數(shù)携茂,當(dāng)執(zhí)行responseCallback時(shí)會(huì)執(zhí)行_doSend方法你踩,并且以responseId為key傳入了callbackResponseId
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
//找到相應(yīng)的handler并執(zhí)行
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
此處關(guān)鍵在于responseCallback是OC傳入的代碼塊,應(yīng)該由OC執(zhí)行讳苦,所以JS調(diào)用_doSend方法改變iframe.src發(fā)起請(qǐng)求带膜,通知OC執(zhí)行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;
}
然后OC攔截到請(qǐng)求的操作之前已經(jīng)過了一遍,那不同的地方在哪呢鸳谜,那就是有了responseId
- (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"];
if (responseId) {//有了responseId膝藕,這很關(guān)鍵,根據(jù)responseId找到responseCallback并執(zhí)行
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (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
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
找到了之前存儲(chǔ)的responseCallback并執(zhí)行咐扭,到此OC調(diào)用JS注冊(cè)的函數(shù)流程完畢芭挽。
三.總結(jié)
受限于JS知識(shí)水平不足滑废,有些地方分析的不太好,不對(duì)之處還望指正袜爪,不勝感激蠕趁!另外我還寫了一篇JavaScriptCore的使用介紹,JavaScriptCore是蘋果提供的一套用于與JS交互的框架辛馆,感興趣的朋友可以點(diǎn)這里看一下