iOS WebView JavascriptBridgeの原理解析
29260 ワード
前言
WebView JavascriptBridgeのgithubアドレス
1、OC環境初期化
2、OC環境登録方法
//このコードは
上記では
まず、
このファイルにおいても、
そして私たちは書類の一番後ろに次のコードがあります.このコードの役割は、
WebView Javascript Bridge.uhandleMessage FroomObjC('「calbackId」:「bjucbu 1」,「data」:「OC呼び出しJS方法」:「OC呼び出しJS方法のパラメータ」)、「handleName」:「OC呼び出しJS提供方法」
つまり、
まずExampleAPP.htmlのbridge.callHandler方法を通じて、ここのbridgeはwindow.WebView JavascriptBridgeの対象です.
実は今考えてみます.原理は簡単です.
OC環境とjavascript環境にはそれぞれ一つのbridgeオブジェクトが保存されています.中にはrequestId、calbackId、及び各idに対応する具体的な実現が維持されています.
OCはjavascript環境のwindow.WebView JavascriptBridgeオブジェクトを通じて具体的な方法を探して実行します.
javascriptは、iframeのsrcを変えてwebviewの代理方法webViewを出発します.webView decidepolicyForNavitionAction:(WKNavigations*)navitional Action decisionhandler:からこのメッセージを送信します.
実はここではwebviewとOCの橋渡し問題を解析しただけです.他のwebviewでの要求ブロッキング、プログレスバーの追加、オペレータのハイジャック、インタラクティブルールの整理などの問題はここではまだ触れていません.
WebView JavascriptBridgeのgithubアドレス
WebViewJavascriptBridge
は、現在最も人気のある最も成功したOC
とWeb
との相互作用によって実現されるべきである.OC
でjavascript
方法を呼び出すことができますが、逆にjavascript
でOC
方法を呼び出すことはできません.したがって、WebViewJavascriptBridge
の実装プロセスは、OC
環境およびjavascript
環境においてそれぞれ1つの相互呼出し情報を保存することである.各呼び出しの間にid
とcallbackid
があり、2つの環境に対応する処理を見つける.1、OC環境初期化
OC
環境の初期化から始まります.// OC WKWebViewJavascriptBridge 。
+ (instancetype)bridgeForWebView:(WKWebView*)webView {
WKWebViewJavascriptBridge* bridge = [[self alloc] init];
//
[bridge _setupInstance:webView];
[bridge reset];
return bridge;
}
//
- (void) _setupInstance:(WKWebView*)webView {
_webView = webView;
_webView.navigationDelegate = self;
_base = [[WebViewJavascriptBridgeBase alloc] init];
_base.delegate = self;
}
- (id)init {
if (self = [super init]) {
self.messageHandlers = [NSMutableDictionary dictionary];
self.startupMessageQueue = [NSMutableArray array];
self.responseCallbacks = [NSMutableDictionary dictionary];
_uniqueId = 0;
}
return self;
}
//message Handlers OC環境登録を保存するための方法であり、keyは方法名であり、valueはこの方法に対応するコールバックblock/startup Message Queを保存するためのものであり、正直なところ、javascirpt環境に送信する必要があるメッセージです.レスポンスCallbacksは、Javascript環境でOCが相互に起動するコールバックモジュールを保存するために使用されます._を通すuniqueIdにタイムスタンプを加えて、各呼び出しのコールバックを決定します.javascript
との間の相互作用に関するすべての情報はmessageHandlers
およびresponseCallbacks
に記憶されている.これらの2つの属性は、OC
環境とjavascript
が相互作用する情報を記録している.2、OC環境登録方法
OC
方法を登録し、OC
はJS
に呼び出しを呼び出す方法を提供し、javascript
に保存する.[_bridge registerHandler:@"OC JS " handler:^(id data, WVJBResponseCallback responseCallback) {
//NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"OC JS ");
}];
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
3、Web環境初期化messageHandlers
環境のWeb
をロードして、ここはhtml
ファイルで、私は非重要な部分を削除しました.function setupWebViewJavascriptBridge(callback) {
// , false
if (window.WebViewJavascriptBridge) {
var result = callback(WebViewJavascriptBridge);
return result;
}
// , false
if (window.WVJBCallbacks) {
var result = window.WVJBCallbacks.push(callback);
return result;
}
// callback 。
window.WVJBCallbacks = [callback];
// WebViewJavascriptBridge_JS.js
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);
}
//ExampleAPP.html
が実行するときに着信するパラメータは、一つの方法である.function callback(bridge) {
var uniqueId = 1
// WEB bridge
bridge.registerHandler('OC JS ', function(data, responseCallback) {
log('OC JS ', data)
var responseData = { 'JS OC ':' !' }
log('OC JS ', responseData)
responseCallback(responseData)
})
};
//駆動全setupWebViewJavascriptBridge
の初期化setupWebViewJavascriptBridge(callback);
hander
関数を呼び出し、この関数が入ってきたsetupWebViewJavascriptBridge
も関数です.callback
関数には、callback
環境に登録されたjavascript
がOC
から提供される方法を呼び出す方法があります.JS
の実装中に、最初の初期化でなければ、setupWebViewJavascriptBridge
またはwindow.WebViewJavascriptBridge
の二つの判断によって戻ってくることがわかった.window.WVJBCallbacks
は、iframe
の中のウィンドウとして理解でき、webview
のiframe
の属性を変更すると、私たちのブラウザがリンクのジャンプを実現したに相当する.例えば、wwww.baidu.comからwwww.google.comにジャンプします.以下のコードの目的はsrc
へのジャンプを実現することです.これにより、https://__bridge_loaded__
環境を初期化するjavascript
の役割を果たす.//このコードは
bridge
のコードをロードする役割を果たすという意味です.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);
WebViewJavascriptBridge_JS.js
がジャンプしている限り、webview
のプロキシ方法を呼び出していることを知っています.私たちは重点的に次の代理方法を見ます.- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
NSLog(@" URL%@",url);
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
// WebViewJavascriptBridge , 。 。
if ([_base isWebViewJavascriptBridgeURL:url]) {
//1 JS
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
// WEB
} else if ([_base isQueueMessageURL:url]) {
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
}
// webview , 。
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
[_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
このコードでは、まずwebview
を通じて、通常のジャンプか[_base isWebViewJavascriptBridgeURL:url]
のジャンプかを判断します.webViewjavascriptBridege
が初期化__bridge_loaded__
環境であることを示すメッセージであれば、javascript
であれば、__wvjb_queue_message__
メッセージを送信することを示す.javascript
は明らかに第1のメッセージである.https://__bridge_loaded__
具体的な判断論理コードは以下の通りである.#define kOldProtocolScheme @"wvjbscheme"
#define kNewProtocolScheme @"https"
#define kQueueHasMessage @"__wvjb_queue_message__"
#define kBridgeLoaded @"__bridge_loaded__"
// WebViewJavascriptBridge
- (BOOL)isWebViewJavascriptBridgeURL:(NSURL*)url {
if (![self isSchemeMatch:url]) {
return NO;
}
BOOL result = [self isBridgeLoadedURL:url] || [self isQueueMessageURL:url];
return result;
}
/*
WebViewJavascriptBridge
*/
- (BOOL)isSchemeMatch:(NSURL*)url {
NSString* scheme = url.scheme.lowercaseString;
BOOL result = [scheme isEqualToString:kNewProtocolScheme] || [scheme isEqualToString:kOldProtocolScheme];
return result;
}
// WebViewJavascriptBridge WebViewJavascriptBridge 。
- (BOOL)isQueueMessageURL:(NSURL*)url {
NSString* host = url.host.lowercaseString;
return [self isSchemeMatch:url] && [host isEqualToString:kQueueHasMessage];
}
// https://__bridge_loaded__
- (BOOL)isBridgeLoadedURL:(NSURL*)url {
NSString* host = url.host.lowercaseString;
BOOL result = [self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded];
return result;
}
次にOC
方法を呼び出すと、この方法は[_base injectJavascriptFile]
の方法をWebViewJavascriptBridge_JS.js
に注入して実行し、webview
の環境を初期化するjavascript
の役割を果たす.// WebViewJavascriptBridge_JS.js
- (void)injectJavascriptFile {
NSString *js;
//WebViewJavascriptBridge_JS.js WebViewJavascriptBridge_JS.m , 。
if (true) {
js = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WebViewJavascriptBridge_JS.js" ofType:nil] encoding:NSUTF8StringEncoding error:nil];
}else{
js = WebViewJavascriptBridge_js();
}
// javascript webview , 。
[self _evaluateJavascript:js];
// javascript , startupMessageQueue 。 。
if (self.startupMessageQueue) {
NSArray* queue = self.startupMessageQueue;
self.startupMessageQueue = nil;
for (id queuedMessage in queue) {
[self _dispatchMessage:queuedMessage];
}
}
}
// javascript webview
- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand {
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
return NULL;
}
3、brige
解析上記では
WebViewJavascriptBridge_JS.js
注入方法をjavascript
に説明しました.具体的なコードはwebview
というファイルの中の方法です.このファイルのコードを解析することにより、WebViewJavascriptBridge_JS.js
環境のjavascript
がどのように初期化されたかを知ることができる.;(function() {
// , 。
if (window.WebViewJavascriptBridge) {
return;
}
if (!window.onerror) {
window.onerror = function(msg, url, line) {
console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
}
}
// 。
var messagingIframe;
//
var sendMessageQueue = [];
//
var messageHandlers = {};
// , 。
var CUSTOM_PROTOCOL_SCHEME = 'https';
var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
//oc js
var responseCallbacks = {};
// id
var uniqueId = 1;
//
var dispatchMessagesWithTimeoutSafety = true;
//web
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
//web OC
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName: handlerName, data: data }, responseCallback);
}
function disableJavscriptAlertBoxSafetyTimeout() {
dispatchMessagesWithTimeoutSafety = false;
}
// JSON
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
//OC JS
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
// ,OC WebViewJavascriptBridge JS 。
window.WebViewJavascriptBridge = {
registerHandler: registerHandler,
callHandler: callHandler,
disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
};
// OC 。
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 {//
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData });
};
}
// JS
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
// JS
handler(message.data, responseCallback);
}
}
}
}
// JS OC, 。
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
// ID
responseCallbacks[callbackId] = responseCallback;
// ID , 。
message['callbackId'] = callbackId;
}
//
sendMessageQueue.push(message);
// JS OC
// webview ,
//webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler JS OC
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
//messagingIframe.body.style.backgroundColor="#0000ff";
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);
// _disableJavascriptAlertBoxSafetyTimeout , OC , 。
registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
// _callWVJBCallbacks
setTimeout(_callWVJBCallbacks, 0);
// WEB 。 WEB hander bridge 。
// WEB callback 。
function _callWVJBCallbacks() {
var callbacks = window.WVJBCallbacks;
delete window.WVJBCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](WebViewJavascriptBridge);
}
}
})();
実は、bridge
ファイル全体が即座に実行されるjs
方法であることを発見しました.まず、
javascript
オブジェクトを初期化することを発見した.また、このオブジェクトはWebViewJavascriptBridge
オブジェクトに与えられた値であり、ここでwindow
オブジェクトはwindow
として理解される.したがって、webview
環境において、OC
方法を呼び出すなら、js
によって具体的な方法を加えて呼び出すことができる.window.WebViewJavascriptBridge
オブジェクトには、WebViewJavascriptBridge
の環境注入がjavascript
に呼び出される方法OC
があり、registerHandler
は、javascript
の環境方法のOC
を呼び出す.callHandler
この方法の役割は、_fetchQueue
環境の方法シーケンスをjavascript
文字列に変換し、JSON
環境に再変換することである.OC
は、_handleMessageFromObjC
を処理してOC
に環境を与える方法である.このファイルにおいても、
javascript
のiframe
ジャンプ機能を実装するwebview
が初期化され、これにより、url
プロキシ方法の呼び出しが励起される. messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
//messagingIframe.body.style.backgroundColor="#0000ff";
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);
上のwebview
はsrc
です.これはhttps://__wvjb_queue_message__/
が送信したjavascript
の第1のメッセージであり、OC
環境のOC
と同様に、startupMessageQueue
t環境初期化が完了したら、javascrip
がjavascript
に送信するメッセージを直ちに送信する.そして私たちは書類の一番後ろに次のコードがあります.このコードの役割は、
OC
におけるExampleApp.html
方法を直ちに実行することである.callback
に入力されたcallback
パラメータは、ここで初期化されたbridge
オブジェクトである. // _callWVJBCallbacks
setTimeout(_callWVJBCallbacks, 0);
// WEB 。 WEB hander bridge 。
// WEB callback 。
function _callWVJBCallbacks() {
var callbacks = window.WVJBCallbacks;
delete window.WVJBCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](WebViewJavascriptBridge);
}
}
ここまで、window.WebViewJavascriptBridge
環境およびOC
環境のjavascript
は確立された.bridege
及びOC
の環境にはいずれもjavascript
オブジェクトがあり、このオブジェクトは登録された各方法及びコールバックを保持し、それぞれのメッセージキュー、コールバックbridge
、id
などの一連の情報を保持している.requestId
はOC
にメッセージを送る.WEB
は、OC
環境を呼び出す方法であり、javascript
のExampleApp.html
に登録されている方法を呼び出す.// OC .ExampleWKWebViewController.m 。
- (void)callHandler:(id)sender {
id data = @{ @"OC JS ": @"OC JS " };
[_bridge callHandler:@"OC JS " data:data responseCallback:^(id response) {
// NSLog(@"testJavascriptHandler responded: %@", response);
}];
}
/*
handerName:OC JS
data:{@"OC JS ":@"OC JS "}
responseCallback: block
*/
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}
すべての情報をbridge.registerHandler
という辞書に保存します.中にはパラメータmessage
、コールバックdata
、メッセージ名IDcallbackId
が埋め込まれています.具体的には以下の通りです- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionary dictionary];
if (data) {
message[@"data"] = data;
}
if (responseCallback) {
NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
}
if (handlerName) {
message[@"handlerName"] = handlerName;
}
[self _queueMessage:message];
}
handlerName
メッセージを順番に並べ、OC
環境のフォーマットに変換する.次に、メインスレッドでjavascript
を呼び出します.// WEB
- (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:@"
" withString:@"\
"];
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];
});
}
}
具体的に注入されたjavascript文字列は以下の通りです.WebView Javascript Bridge.uhandleMessage FroomObjC('「calbackId」:「bjucbu 1」,「data」:「OC呼び出しJS方法」:「OC呼び出しJS方法のパラメータ」)、「handleName」:「OC呼び出しJS提供方法」
つまり、
_evaluateJavascript
環境におけるjavascript
オブジェクトのBridge
方法によってである._handleMessageFromObjC
に行って、WebViewJavascriptBridege_JS.js
の処理を見ます.// OC 。
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 {//
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData });
};
}
// JS
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
// JS
handler(message.data, responseCallback);
}
}
}
}
上記のコードは分かりやすいですが、メッセージに_handleMessageFromObjC
があれば、折り返しという意味です.直接呼び出しcallbackId
方法は、OCに情報を返す.そうでなければ、_doSend
環境アクティブコールWeb
の場合である.OC
、callbackID
、handlerName
を一つのresponseCallback
オブジェクトにパッケージ化して保存します.message
を介してメッセージをOC
環境に送信する.bridge
の具体的な実現を見てみましょう.// JS OC, 。
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
// ID
responseCallbacks[callbackId] = responseCallback;
// ID , 。
message['callbackId'] = callbackId;
}
//
sendMessageQueue.push(message);
// JS OC
// webview ,
//webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler JS OC
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
その中で最も重要なのは、_doSend
のOC
を変更することによって後の方である.これにより、_doSend
のプロキシ方法iframe
がトリガされ、messagingIframe.src
においてwebview
の環境によってトリガされるコールバックが処理される.具体的には以下の通りですif ([_base isWebViewJavascriptBridgeURL:url]) {
// JS
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
// WEB
} else if ([_base isQueueMessageURL:url]) {
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
}
ここでwebView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
方法を行きます.OC
を呼び出してjavascript
に対するコールバック情報を取得する.// WEB JSON
- (NSString *)webViewJavascriptFetchQueyCommand {
return @"WebViewJavascriptBridge._fetchQueue();";
}
//// WEB OC OC
- (void)WKFlushMessageQueue {
NSString *js = [_base webViewJavascriptFetchQueyCommand];
[_webView evaluateJavaScript:js completionHandler:^(NSString* result, NSError* error) {
if (error != nil) {
NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
}
// WEB OC OC
[_base flushMessageQueue:result];
}];
}
[self WKFlushMessageQueue];
からWebViewJavascriptBridge._fetchQueue()
へのコールバックメッセージを取得した後、javascript
のOC
から戻った情報をjavascript
環境のOC
が識別できる情報に加工する.具体的な実装を見つけて実行します.// WEB 。
- (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) {
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);
}
}
}
ここではhandlerメソッドを呼び出し、javascriptからのレスポンスIdを通じて対応するWVJBRIesponseCallbackを取得します.このblockを実行します.ここでOCからjavascriptにメッセージを送り、javascriptはOCにメッセージを返すプロセスを終了しました.WEBからOCにメッセージを送りますまずExampleAPP.htmlのbridge.callHandler方法を通じて、ここのbridgeはwindow.WebView JavascriptBridgeの対象です.
bridge.callHandler('OC JS ',params, function(response) {
log('JS OC ', response)
})
次にWindows.WebView JavascriptBridgeのcalHanderメソッドを呼び出します.//web OC
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName: handlerName, data: data }, responseCallback);
}
そしてWebView JavascriptBridgeを呼び出します.JS.jsの方法は具体的な操作を行います.具体的にはOC呼び出しjavascriptと同じです.説明しません.// JS OC, 。
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
// ID
responseCallbacks[callbackId] = responseCallback;
// ID , 。
message['callbackId'] = callbackId;
}
//
sendMessageQueue.push(message);
// JS OC
// webview ,
//webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler JS OC
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
締め括りをつける実は今考えてみます.原理は簡単です.
OC環境とjavascript環境にはそれぞれ一つのbridgeオブジェクトが保存されています.中にはrequestId、calbackId、及び各idに対応する具体的な実現が維持されています.
OCはjavascript環境のwindow.WebView JavascriptBridgeオブジェクトを通じて具体的な方法を探して実行します.
javascriptは、iframeのsrcを変えてwebviewの代理方法webViewを出発します.webView decidepolicyForNavitionAction:(WKNavigations*)navitional Action decisionhandler:からこのメッセージを送信します.
実はここではwebviewとOCの橋渡し問題を解析しただけです.他のwebviewでの要求ブロッキング、プログレスバーの追加、オペレータのハイジャック、インタラクティブルールの整理などの問題はここではまだ触れていません.