当前位置: 首页 > 编程日记 > 正文

WebViewJavascriptBridge原理解析

基本说明

我们的项目是一个OC与javascript重度交互的app,OC与javascript交互的那部分是在WebViewJavascriptBridge的github地址的基础上修改的,WebViewJavascriptBridge应该是当前最流行最成功的OC与Web交互实现了。最近看了一下他的实现原理,顺便也为后面项目扩展打下基础。
为了简化讲解过程,我忽略了UIWebView的实现过程,只解析WKWebView的实现过程。

我们可以在OC中调用javascript方法,但是反过来不能在javascript中调用OC方法。所以WebViewJavascriptBridge的实现过程就是在OC环境和javascript环境各自保存一个相互调用的信息。每一个调用之间都有id和callbackid来找到两个环境对应的处理。下图是我对于每个类的讲解:

img

  • nouse文件夹下面的文件是与UIWebView相关的东西,我们暂时不管,基本原理和WKWebView一样。其中WebViewJavascriptBridge_JS.m中是javascript代码,为了方便理解,我直接新建了一个WebViewJavascriptBridge_JS.js文件来代替,方便后面解析。

  • WebViewJavascriptBridge_JS.js文件中是javascript环境的bridge初始化和处理,里面负责接收oc发给javascript的消息,并且把javascript环境的消息发送给oc。

  • WKWebViewJavascriptBridge.m主要负责OC环境的消息处理,并且把OC环境的消息发送给javascript环境。

  • WebViewJavascriptBridgeBase.m主要实现了OC环境的bridge初始化和处理。

  • ExampleApp.html主要用于模拟生产环境下的web端。

初始化过程

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;
}//messageHandlers用于保存OC环境注册的方法,key是方法名,value是这个方法对应的回调block
//startupMessageQueue用于保存是实话过程中需要发送给javascirpt环境的消息。
//responseCallbacks用于保存OC于javascript环境相互调用的回调模块。通过_uniqueId加上时间戳来确定每个调用的回调。
- (id)init {if (self = [super init]) {self.messageHandlers = [NSMutableDictionary dictionary];self.startupMessageQueue = [NSMutableArray array];self.responseCallbacks = [NSMutableDictionary dictionary];_uniqueId = 0;}return self;
}

所有与javascript之间交互的信息都存储在messageHandlersresponseCallbacks中。这两个属性记录了OC环境与javascript交互的信息。

2、OC环境注册方法

注册一个OC方法OC提供方法给JS调用给javascript调用,并且把他的回调实现保存在messageHandlers中。

[_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环境初始化

加载Web环境的html,这里就是ExampleAPP.html文件,我删除了非关键部分。

function setupWebViewJavascriptBridge(callback) {//第一次调用这个方法的时候,为falseif (window.WebViewJavascriptBridge) {var result = callback(WebViewJavascriptBridge);return result;}//第一次调用的时候,也是falseif (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);
}//setupWebViewJavascriptBridge执行的时候传入的参数,这是一个方法。
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)})
};
//驱动所有hander的初始化
setupWebViewJavascriptBridge(callback);

我们调用setupWebViewJavascriptBridge函数,并且这个函数传入的callback也是一个函数。callback函数中有我们在javascript环境中注册的OC调用JS提供的方法方法。setupWebViewJavascriptBridge的实现过程中我们可以发现,如果不是第一次初始化,会通过window.WebViewJavascriptBridge或者window.WVJBCallbacks两个判断返回。

iframe可以理解为webview中的窗口,当我们改变iframe的src属性的时候,相当于我们浏览器实现了链接的跳转。比如从www.baidu.com跳转到www.google.com。下面这段代码的目的就是实现一个到https://__bridge_loaded__的跳转。从而达到初始化javascript环境的bridge的作用。

//这段代码的意思就是执行加载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);

我们知道只要webview有跳转,就会调用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);}
}

在这段代码中,我们首先通过[_base isWebViewJavascriptBridgeURL:url]来判断是否是普通的跳转还是webViewjavascriptBridege的跳转。如果是__bridge_loaded__表示是初始化javascript环境的消息,如果是__wvjb_queue_message__则表示是发送javascript消息。https://__bridge_loaded__显然是第一种消息。OC具体具体判断逻辑代码如下:

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

接下来调用[_base injectJavascriptFile]方法,这个方法的作用就是把WebViewJavascriptBridge_JS.js中的方法注入到webview中并且执行,从而达到初始化javascript环境的brige的作用。

//初始化的是否注入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、WebViewJavascriptBridge_JS.js解析

上面我们讲到了注入javascript方法到webview中。具体的代码就是WebViewJavascriptBridge_JS.js这个文件中的方法。我们通过分析这个文件的代码可以知道javascript环境的bridge是如何初始化的。

;(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 = {};//消息对应的idvar 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();//存储消息的回调IDresponseCallbacks[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);}}
})();

其实我们发现整个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跳转功能,从而激发webview代理方法的调用。

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

上面的src就是https://__wvjb_queue_message__/。这个是javascript发送的OC的第一条消息,目的和上面OC环境的startupMessageQueue一样,就是在javascript环境初始化完成以后,把javascript要发送给OC的消息立即发送出去。

然后我们看文件的最后面有如下代码。这段代码的作用就是立即执行ExampleApp.html中的callback方法。callback中传入的bridge参数就是我们这里初始化的window.WebViewJavascriptBridge对象。

    //执行_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);}}

直到这里,OC环境和javascript环境的bridege都建立完毕。OC和javascript环境都有一个bridge对象,这个对象都保存着注册的每个方法和回调,并且维护着各自的消息队列、回调id、requestId等一系列信息。

OC发消息给WEB

OC要调用javascript环境的方法,其实就是调用ExampleApp.html中的bridge.registerHandler注册的方法。

//点击按钮开始一个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];
}

把所有信息存入一个名字为message的字典中。里面拼装好参数data、回调IDcallbackId、消息名字handlerName。具体如下:

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

把OC消息序列化、并且转化为javascript环境的格式。然后在主线程中调用_evaluateJavascript。

//把消息发送给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:@"\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];});}
}

具体注入的javascript字符串如下:

WebViewJavascriptBridge._handleMessageFromObjC('{\"callbackId\":\"objc_cb_1\",\"data\":{\"OC调用JS方法\":\"OC调用JS方法的参数\"},\"handlerName\":\"OC调用JS提供的方法\"}');

其实就是通过javascript环境中的Bridge对象的_handleMessageFromObjC方法。下面我们去WebViewJavascriptBridege_JS.js中看_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);}}}
}

上面这段代码很容易理解,其实就是如果消息中有callbackId则表示是一个回调。直接调用_doSend方法把信息返回OC。否则就是Web环境主动调用OC的情况。此时把callbackID、handlerName、responseCallback封装进一个message对象中保存起来(其实你会发现和OC环境的bridge处理一样)。然后通过_doSend发消息发送到OC环境。下面我们看看_doSend的具体实现:

//把消息从JS发送到OC,执行具体的发送操作。
function _doSend(message, responseCallback) {if (responseCallback) {var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();//存储消息的回调IDresponseCallbacks[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;
}

其中最重要还是最后面的通过改变iframe的messagingIframe.src。从而触发webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler从而在OC中处理javascript环境触发过来的回调。具体如下:

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

这里会走[self WKFlushMessageQueue];方法。然后通过调用WebViewJavascriptBridge._fetchQueue()来获取javascript给OC的回调信息。

//获取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];}];
}

获取到javascript给OC的回调消息以后,然后把javascript的bridge返回的信息加工处理成OC环境的bridge能识别的信息。从而找到具体的实现执行。

//把从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传过来的responseId获取对应的WVJBResponseCallback。然后执行这个block。到这里从OC发送消息到javascript并且javascript返回消息给OC的流程走完了。

WEB发消息给OC

首先通过ExampleAPP.html中的bridge.callHandler方法,这里的bridge就是window.WebViewJavascriptBridge对象:

bridge.callHandler('OC提供方法给JS调用',params, function(response) {log('JS调用OC的返回值', response)
})

接下来调用window.WebViewJavascriptBridge中的callHander方法

//web端调用一个OC注册的消息
function callHandler(handlerName, data, responseCallback) {if (arguments.length == 2 && typeof data == 'function') {responseCallback = data;data = null;}_doSend({ handlerName: handlerName, data: data }, responseCallback);
}

然后调用WebViewJavascriptBridge_JS.js中的方法执行具体的操作。具体就和OC调用javascript过程一样了,就不解释了。

//把消息从JS发送到OC,执行具体的发送操作。
function _doSend(message, responseCallback) {if (responseCallback) {var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();//存储消息的回调IDresponseCallbacks[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,callbackId,以及每个id对应的具体实现。

  • OC通过javascript环境的window.WebViewJavascriptBridge对象来找到具体的方法,然后执行。

  • javascript通过改变iframe的src来出发webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler从而实现把javascript消息发送给OC这个功能。

其实这里只是解析了webview与OC交互的桥接问题,其他比如webview中的请求拦截、添加进度条、运营商劫持、如何组织交互规则等问题这里还没有涉及。这些在我们项目中运用,具体就不抽出来了。

最后,具体的源码在github地址。

相关文章:

DataGrid连接Access的快速分页法(2)——SQL语句的选用(升序与降序)

作者&#xff1a;黎波 一、相关概念 在 ACCESS 数据库中&#xff0c;一个表的主键&#xff08;PRIMARY KEY&#xff0c;又称主索引&#xff09;上必然建立了唯一索引&#xff08;UNIQUE INDEX&#xff09;&#xff0c;因此主键字段的值是不会重复的。并且索引页依据索引列的值…

从谷歌AutoML到百度EasyDL,AI大生产时代,调参师不再是刚需

出品 | AI科技大本营头图 | 付费下载于视觉中国2018 年&#xff0c;Google Cloud 宣布将 AutoML 作为机器学习产品的一部分。至此&#xff0c;AutoML 开始进入大众的视野。 实际上&#xff0c;2013 年AutoWEKA的发布可以算作AutoML的开端&#xff1b;2014 年&#xff0c;ICML开…

Python 语法小知识

为什么80%的码农都做不了架构师&#xff1f;>>> 序列解包 将含有多个值的序列解开&#xff0c;然后把值存放到变量中&#xff0c;当函数或者方法返回元组时这个特性很有用&#xff0c;可以把返回的序列值直接赋值给变量&#xff0c;在序列解包时等号两边的元素个数…

CSS布局之-水平垂直居中

对一个元素水平垂直居中&#xff0c;在我们的工作中是会经常遇到的&#xff0c;也是CSS布局中很重要的一部分&#xff0c;本文就来讲讲CSS水平垂直居中的一些方法。另外&#xff0c;文中的css都是用less书写的&#xff0c;如果看不懂less&#xff0c;可以把我给的demo链接打开&…

DataGrid连接Access的快速分页法——动态生成SQL语句

作者&#xff1a;黎波using System;using System.Text;namespace Paging{/// <summary>/// FastPaging 的摘要说明。/// </summary>public class FastPaging {private FastPaging() {}/// <summary>/// 获取根据指定字段排序并分页查询的 SELECT 语句。/// &…

一文读懂机器学习“数据中毒”

作者 | Ben Dickson翻译 | 火火酱~出品 | AI科技大本营头图 | 付费下载于视觉中国在人类的眼中&#xff0c;下面的三张图片分别展示了三样不同的东西&#xff1a;一只鸟、一只狗和一匹马。但对于机器学习算法来说&#xff0c;这三者或许表示同样的东西:一个有黑边的白色小方框。…

chartee

2019独角兽企业重金招聘Python工程师标准>>> 一个绘制图表的类库&#xff0c;支持绘制股票的K线图&#xff0c;还可以绘制曲线、柱状图等等。 Code4App编译测试&#xff0c;测试环境&#xff1a;Xcode 4.3, iOS 5.0。 转载:http://www.adobex.com/ios/source/detail…

C语言存储类关键字

1、static这个关键字有三种用法&#xff1a;&#xff08;1&#xff09;第一种是用来修饰局部变量&#xff0c;使之成为静态局部变量&#xff1b;静态局部变量存储在数据段/bss段中&#xff0c;作用域是代码块作用域&#xff0c;生命周期是程序生命周期&#xff0c;链接属性是无…

显示DataGrid序号的一个适用的方法

作者Blog&#xff1a;http://blog.csdn.net/wangj2001/如果数据量小的话没有问题&#xff0c;一旦数据量大&#xff0c;显示特别慢&#xff0c;还有个缺点就是拖动行高时行号不随行高的变化而变动&#xff0c;出现是几个序号在一个单元格中显示。我自己对他们的算法进行总结&am…

Integer的自动缓存

2019独角兽企业重金招聘Python工程师标准>>> Interger装箱有个自动缓存的概念 Integer a 100;Integer b 100;Integer c 200;Integer d 200;System.out.println(a b); //trueSystem.out.println(c d); //false Integer是对象&#xff0c;比较的是对象在内存中…

崩溃!双十一第 4 天,某互联网公司黄了?

01打折这么狠是不是要黄了&#xff1f;&#xff1f;今年的双十一&#xff0c;开始地比以往早一些&#xff01;不知道各位程序员小哥哥们的战绩如何&#xff0c;是不是已经被一轮又一轮的定金、尾款&#xff0c;折腾到数学细胞耗尽&#xff1f;付款了也不清楚自己有没有真正的「…

FreeBSD设备驱动管理介绍(BSP: Ti AM335x)

这段时间一直在忙FreeBSD驱动移植的项目&#xff0c;因此对FreeBSD做了一定的了解&#xff0c;鉴于网上对于FreeBSD的设备驱动资料较少&#xff0c;在这里给出本人对于FreeBSD驱动管理的理解心得&#xff08;主要是USB驱动管理&#xff09;&#xff0c;希望能对开源开发者有所帮…

视障人士体验自动驾驶:携导盲犬登车,未来有望“自己开”

6月27日&#xff0c;滴滴出行首次面向公众开放自动驾驶服务。用户可通过滴滴APP线上报名&#xff0c;审核通过后&#xff0c;将能在上海自动驾驶测试路段&#xff0c;免费呼叫自动驾驶车辆进行试乘体验。现阶段&#xff0c;滴滴自动驾驶载人测试范围仅限于在上海开放测试道路上…

Listview获取选中行的值

一般情况请注意别先删除了选中行&#xff0c;又去使用。那就会导致找不到选中行。。。。。哥犯了这个错误。。。找了很长时间问题if (this.lstwlview.SelectedIndices.Count > 0) { if (MessageBox.Show("确认删除该条码&#xff1f;",…

asp.net中DataGrid性能测试

作者Blog&#xff1a;http://blog.csdn.net/yzdy/ 测试环境&#xff1a;数据库服务器:2.4G P4 CPU&#xff0c;2G 内存&#xff0c;Windows Advanced Server 2000&#xff0c;SQL Server 2000Web服务器&#xff1a;2.4G P4 CPU&#xff0c;1G 内存&#xff0c;Windows Advanced…

javassist学习笔记

2019独角兽企业重金招聘Python工程师标准>>> 介绍&#xff1a;www.javassist.org/ javassist、ASM 对比 1、javassist是基于源码级别的API比基于字节码的ASM简单。 2、基于javassist开发&#xff0c;不需要了解字节码的一些知识&#xff0c;而且其封装的一些工具类可…

金融领域首个开源中文BERT预训练模型,熵简科技推出FinBERT 1.0

出品 | AI科技大本营头图 | CSDN付费下载于东方IC为了促进自然语言处理技术在金融科技领域的应用和发展&#xff0c;熵简科技 AI Lab 近期开源了基于 BERT 架构的金融领域预训练语言模型 FinBERT 1.0。据悉&#xff0c;这是国内首个在金融领域大规模语料上训练的开源中文BERT预…

raspberry pi下使用mp3blaster播放mp3音乐

首先&#xff1a;sudo apt-get install mp3blaster mp3blaster wode.mp3会报错 但是加padsp mp3blaster wode.mp3 就可以正常播放了

把Excel文件中的数据读入到DataGrid中

作者Blog&#xff1a;http://blog.csdn.net/net_lover/使用Excel文件做为DataGrid的数据源是非常简单的&#xff0c;一旦数据被装载进来&#xff0c;就可以把数据再保存进SQL Server或XML中。我们只需要简单地使用OLE DB Provider 来访问Excel文件&#xff0c;然后返回DataSet即…

Vue 数组中更新属性值后,视图不更新,等待其他元素更新后会触发的解决办法...

因为 JavaScript 的限制&#xff0c;Vue.js 不能检测到下面数组变化&#xff1a; 直接用索引设置元素&#xff0c;如 vm.items[0] {}&#xff1b; 修改数据的长度&#xff0c;如 vm.items.length 0。 this.examineIntro.questionList[0].selList[1].url url;为了解决问题 (…

DeepMind 最新论文解读:首次提出离散概率树中的因果推理算法

翻译 | 高卫华出品 | AI科技大本营头图 | CSDN付费下载自视觉中国当前&#xff0c;一些前沿AI研究人员正在寻找用于表示上下文特定的因果依赖关系清晰的语义模型&#xff0c;这是因果归纳所必需的&#xff0c;在 DeepMind的算法中可看到这种概率树模型。概率树图用于表示概率空…

使用c#+(datagrid控件)编辑xml文件

作者Blog&#xff1a;http://blog.csdn.net/ouyang76cn/ 使用c#(datagrid控件)编辑xml文件 这个源码是我根据网上一个vb.net编辑xml文件的原理用c#重写的。除重用xml文件外.并未重用任何代码&#xff01;. 这小段代码&#xff0c;可对xml文件的记录进行删除&#xff0c;修改&am…

HorizontalTable

2019独角兽企业重金招聘Python工程师标准>>> HorizontalTable 实现了可水平滚动的 TableView。 转载:http://www.adobex.com/ios/source/details/00000761.htm 转载于:https://my.oschina.net/u/868244/blog/106055

敏捷软件开发的12个原则

作为一个软件工程师&#xff0c;软件设计和开发是最重要的技能&#xff0c;但是&#xff0c;从整个产品的角度上讲&#xff0c;项目管理能力比开发能力更重要&#xff0c;本文摘自Robert大叔的《敏捷软件开发》&#xff0c;粗体是Robert大叔的话&#xff0c;细体是我的理解。 1…

JAVE EE 企业级开发之从零开始学JAVA【51CTO技术论坛】

http://bbs.51cto.com JAVE EE 企业级开发之从零开始学JAVA 从零开始学JAVA&#xff1f;YES!本刊内容全部为午饭redking整理、撰写&#xff0c;所涉及内容均为原创&#xff0c;非常适合入门级JAVA新人学习&#xff0c;从理论到实际&#xff0c;跟随redking一起学习JAVA&#x…

打工人,打工魂,抽终身会员,成为人上人!

今年双11&#xff0c;CSDN直接来了个前所未有的福利大奖&#xff01;CSDN终身会员还有全站课程免费学&#xff01;喜欢吗&#xff1f;哈哈哈哈&#xff0c;还没完呢&#xff01;买CSDN年会员&#xff0c;不要&#xffe5;299&#xff0c;只要&#xffe5;199&#xff01;超级年…

如何创建一个用弹出窗口来查看详细信息的超链接列

如何创建一个用弹出窗口来查看详细信息的超链接列出处&#xff1a;www.dotnetjunkie.com 这篇文章来自于一位忠实的DotNetJunkie的建议&#xff0c;他最初发了一封email给我们&#xff0c;要求我们给出一个例子来说明如何在DataGrid中设置一个当用户点击时能够弹出显示其…

HashSet的使用

2019独角兽企业重金招聘Python工程师标准>>> 1.Set中元素是无序的 HashSet setnew HashSet(); set.add("a"); set.add("b"); set.add("c"); set.add("d"); System.out.println(set);//结果&#xff1a;[d,b,c,a]2.Set不允许…

python实现nginx图形界面管理

好久没有更新博客了&#xff0c;最近一直在学习python&#xff0c;于是&#xff0c;在有空闲的时间写下python视图界面管理nginx&#xff0c;写得不够好&#xff0c;不过希望能帮忙到其他童鞋&#xff0c;再次感叹&#xff0c;强大的python,不说了&#xff0c;上代码。 [rootlo…

小马智行获2.67亿美元新融资,估值超53亿美元

11月6日&#xff0c;小马智行&#xff08;Pony.ai&#xff09;宣布获得由加拿大安大略省教师退休基金会&#xff08;Ontario Teachers Pension Plan&#xff0c;OTPP&#xff09;旗下教师创新平台&#xff08;Teachers’ Innovation Platform, TIP&#xff09;领投总计2.67亿美…