基本说明 我们的项目是一个OC与javascript重度交互的app,OC与javascript交互的那部分是在WebViewJavascriptBridge的github地址 的基础上修改的,WebViewJavascriptBridge应该是当前最流行最成功的OC与Web交互实现了。最近看了一下他的实现原理,顺便也为后面项目扩展打下基础。 为了简化讲解过程,我忽略了UIWebView的实现过程,只解析WKWebView的实现过程。
我们可以在OC中调用javascript方法,但是反过来不能在javascript中调用OC方法。所以WebViewJavascriptBridge
的实现过程就是在OC环境和javascript环境各自保存一个相互调用的信息。每一个调用之间都有id和callbackid来找到两个环境对应的处理。下图是我对于每个类的讲解:
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环境的初始化开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 + (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 ; }
所有与javascript之间交互的信息都存储在messageHandlers
和responseCallbacks
中。这两个属性记录了OC环境与javascript交互的信息。
2、OC环境注册方法 注册一个OC方法OC提供方法给JS调用
给javascript调用,并且把他的回调实现保存在messageHandlers
中。
1 2 3 4 5 6 7 8 [_bridge registerHandler:@"OC提供方法给JS调用" handler:^(id data, WVJBResponseCallback responseCallback) { responseCallback(@"OC发给JS的返回值" ); }]; - (void )registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler { _base.messageHandlers[handlerName] = [handler copy ]; }
3、Web环境初始化 加载Web环境的html,这里就是ExampleAPP.html
文件,我删除了非关键部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function setupWebViewJavascriptBridge (callback ) { if (window .WebViewJavascriptBridge) { var result = callback(WebViewJavascriptBridge); return result; } if (window .WVJBCallbacks) { var result = window .WVJBCallbacks.push(callback); return result; } 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 ); } function callback (bridge ) { var uniqueId = 1 bridge.registerHandler('OC调用JS提供的方法' , function (data, responseCallback ) { log('OC调用JS方法成功' , data) var responseData = { 'JS给OC调用的回调' :'回调值!' } log('OC调用JS的返回值' , responseData) responseCallback(responseData) }) }; 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的作用。
1 2 3 4 5 6 7 8 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的代理方法。我们重点看下面这个代理方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - (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; if ([_base isWebViewJavascriptBridgeURL:url]) { if ([_base isBridgeLoadedURL:url]) { [_base injectJavascriptFile]; } else if ([_base isQueueMessageURL:url]) { [self WKFlushMessageQueue ]; } else { [_base logUnkownMessage:url]; } decisionHandler(WKNavigationActionPolicyCancel ); } 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具体具体判断逻辑代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #define kOldProtocolScheme @"wvjbscheme" #define kNewProtocolScheme @"https" #define kQueueHasMessage @"__wvjb_queue_message__" #define kBridgeLoaded @"__bridge_loaded__" - (BOOL )isWebViewJavascriptBridgeURL:(NSURL *)url { if (![self isSchemeMatch:url]) { return NO ; } BOOL result = [self isBridgeLoadedURL:url] || [self isQueueMessageURL:url]; return result; } - (BOOL )isSchemeMatch:(NSURL *)url { NSString * scheme = url.scheme.lowercaseString; BOOL result = [scheme isEqualToString:kNewProtocolScheme] || [scheme isEqualToString:kOldProtocolScheme]; return result; } - (BOOL )isQueueMessageURL:(NSURL *)url { NSString * host = url.host.lowercaseString; return [self isSchemeMatch:url] && [host isEqualToString:kQueueHasMessage]; } - (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的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - (void )injectJavascriptFile { NSString *js; if (true ) { js = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WebViewJavascriptBridge_JS.js" ofType:nil ] encoding:NSUTF8StringEncoding error:nil ]; }else { js = WebViewJavascriptBridge_js(); } [self _evaluateJavascript:js]; if (self .startupMessageQueue) { NSArray * queue = self .startupMessageQueue; self .startupMessageQueue = nil ; for (id queuedMessage in queue) { [self _dispatchMessage:queuedMessage]; } } } - (NSString *) _evaluateJavascript:(NSString *)javascriptCommand { [_webView evaluateJavaScript:javascriptCommand completionHandler:nil ]; return NULL ; }
3、WebViewJavascriptBridge_JS.js解析 上面我们讲到了注入javascript方法到webview中。具体的代码就是WebViewJavascriptBridge_JS.js
这个文件中的方法。我们通过分析这个文件的代码可以知道javascript环境的bridge是如何初始化的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 ;(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__' ; var responseCallbacks = {}; var uniqueId = 1 ; var dispatchMessagesWithTimeoutSafety = true ; function registerHandler (handlerName, handler ) { messageHandlers[handlerName] = handler; } 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 ; } function _fetchQueue ( ) { var messageQueueString = JSON .stringify(sendMessageQueue); sendMessageQueue = []; return messageQueueString; } function _handleMessageFromObjC (messageJSON ) { _dispatchMessageFromObjC(messageJSON); } window .WebViewJavascriptBridge = { registerHandler: registerHandler, callHandler: callHandler, disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC }; 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 }); }; } var handler = messageHandlers[message.handlerName]; if (!handler) { console .log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:" , message); } else { handler(message.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; } messagingIframe = document .createElement('iframe' ); messagingIframe.style.display = 'none' ; messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; document .documentElement.appendChild(messagingIframe); registerHandler("_disableJavascriptAlertBoxSafetyTimeout" , disableJavscriptAlertBoxSafetyTimeout); setTimeout(_callWVJBCallbacks, 0 ); 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代理方法的调用。
1 2 3 4 5 messagingIframe = document .createElement('iframe' ); messagingIframe.style.display = 'none' ; 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对象。
1 2 3 4 5 6 7 8 9 10 11 12 setTimeout(_callWVJBCallbacks, 0 ); 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
注册的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (void )callHandler:(id )sender { id data = @{ @"OC调用JS方法" : @"OC调用JS方法的参数" }; [_bridge callHandler:@"OC调用JS提供的方法" data:data responseCallback:^(id response) { }]; } - (void )callHandler:(NSString *)handlerName data:(id )data responseCallback:(WVJBResponseCallback)responseCallback { [_base sendData:data responseCallback:responseCallback handlerName:handlerName]; }
把所有信息存入一个名字为message的字典中。里面拼装好参数data
、回调IDcallbackId
、消息名字handlerName
。具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (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字符串如下:
1 WebViewJavascriptBridge._handleMessageFromObjC('{\"callbackId\":\"objc_cb_1\",\"data\":{\"OC调用JS方法\":\"OC调用JS方法的参数\"},\"handlerName\":\"OC调用JS提供的方法\"}');
其实就是通过javascript环境中的Bridge对象的_handleMessageFromObjC
方法。下面我们去WebViewJavascriptBridege_JS.js
中看_handleMessageFromObjC
的处理过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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 }); }; } var handler = messageHandlers[message.handlerName]; if (!handler) { console .log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:" , message); } else { handler(message.data, responseCallback); } } } }
上面这段代码很容易理解,其实就是如果消息中有callbackId则表示是一个回调。直接调用_doSend方法把信息返回OC。否则就是Web环境主动调用OC的情况。此时把callbackID、handlerName、responseCallback封装进一个message对象中保存起来(其实你会发现和OC环境的bridge处理一样)。然后通过_doSend发消息发送到OC环境。下面我们看看_doSend的具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
其中最重要还是最后面的通过改变iframe的messagingIframe.src
。从而触发webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
从而在OC中处理javascript环境触发过来的回调。具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 if ([_base isWebViewJavascriptBridgeURL:url]) { if ([_base isBridgeLoadedURL:url]) { [_base injectJavascriptFile]; } else if ([_base isQueueMessageURL:url]) { [self WKFlushMessageQueue ]; } else { [_base logUnkownMessage:url]; } decisionHandler(WKNavigationActionPolicyCancel ); }
这里会走[self WKFlushMessageQueue];
方法。然后通过调用WebViewJavascriptBridge._fetchQueue()
来获取javascript给OC的回调信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (NSString *)webViewJavascriptFetchQueyCommand { return @"WebViewJavascriptBridge._fetchQueue();" ; } - (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); } [_base flushMessageQueue:result]; }]; }
获取到javascript给OC的回调消息以后,然后把javascript的bridge返回的信息加工处理成OC环境的bridge能识别的信息。从而找到具体的实现执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 - (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) { }; } 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
对象:
1 2 3 bridge.callHandler('OC提供方法给JS调用' ,params, function (response ) { log('JS调用OC的返回值' , response) })
接下来调用window.WebViewJavascriptBridge
中的callHander方法
1 2 3 4 5 6 7 8 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过程一样了,就不解释了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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环境和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地址 。