WebViewJavaScriptBridge是IOS中JS和OC交互的常用框架,它利用block的形式处理回调(相关Demo已上传),支持以下两种调用:
基本用法
它的两种使用场景如下:
OC端的方法如下
Method 1 是注册一个OC的方法–testObjcCallback
,handler是JS掉用的内容,responseCallback
是将OC处理返回给JS的回调(对应的是上述第2种调用);
Method 2 是调用JS的方法的testJavascriptHandler
方法,@{ @"foo":@"before ready" }
是需要传递的参数,responseCallback
是将JS处理结果返回给OC的回调(对应的是上述的第1种调用)
JS端的方法如下
Method 1 是JS注册一个方法供OC调用,responseCallback(responseData)
是将处理结果返回OC。
Method 2 是在点击了一个按钮之后JS调用OC的方法,{'foo': 'bar'}
是给OC的参数,response是OC处理后返回给JS的数据。
注:JS中是可以不写;
号的,这和swift一样
JS调用OC,OC将处理结果回调给JS:要想被JS调用,我们首先要注册一个handler,和回调的 block,注册时候以键值对的形式存储这个block,handler,当JS调用OC时调用
webView:shouldStartLoadWithRequest:navigationType:
这个方法,根据JS传来的数据,找到之前保存的Block并且调用,同时新建一个需要把处理结果回调给JS的Blcok,OC处理完结果之后调用刚才创建的Block利用stringByEvaluatingJavaScriptFromString
将处理结果返回给JS。
OC调用JS时与此类似。基于这个流程,我们来看WebViewJavaScriptBridge
的实现过程。
原理
接下来我们来分析从页面加载到OC和JS互相调用的整个过程:
准备工作
当加载HTML文件的时候调用[webView loadHTMLString:appHtml baseURL:baseURL];
,这时会调用:
1 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { |
在这个方法中判断URL的类型,如果是WebViewJavascriptBridgeURL
那么就会判断是BridgeLoadedURL
,QueueMessageURL
还是未知的URL,在首次调用时是返回YES的,然后的URL就是BridgeLoadedURL
,我们在看它的判断条件[self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded];
Scheme是自己设置的https
,那么BridgeLoaded(__bridge_loaded__)
是什么呢?我们看ExampleApp.html
文件,发现它的script
标签中有这么一段代码:
1 | function setupWebViewJavascriptBridge(callback) { |
在这里我们发现了https://__bridge_loaded__
这个iframe的src
,并且在接下来调用setupWebViewJavascriptBridge
时这个src
会当做一个请求,这时会调用shouldStartLoadWithRequest
这个方法。此时就满足了isBridgeLoadedURL
这个请求。这时就会调用
1 | [_base injectJavascriptFile] |
注入一个JS文件,这个JS文件的主要内容是(篇幅问题,有删减):
1 | window.WebViewJavascriptBridge = { |
下面我们来分析下注入的JavaScript的内容。
- 给window对象添加一个属性
WebViewJavascriptBridge
(JS中可以直接给对象添加属性),这个对象包含以下内容:
1) registerHandler:注册调用方法
2)callHandler:调用OC时的方法
3)disableJavscriptAlertBoxSafetyTimeout:超时时弹框是否展示的标示
4)_fetchQueue:获取Queue对象的方法
5)_handleMessageFromObjC:处理OC调用的方法
- 定义了一系列的变量来存储数据
messagingIframe:iframe标签,当我们的WebView加载它的时候,会调用其中的src
,src
就是调用请求的URL。
1)sendMessageQueue:message数组
2)messageHandlers:handler对象 JS中{}表示对象
3)CUSTOM_PROTOCOL_SCHEME:scheme标示
4)QUEUE_HAS_MESSAGE:有Message标识
5)responseCallbacks:回调对象
6)uniqueId:唯一标示ID
进过系列一的剖析,我们明白了使用WebViewJavaScriptBridge
前需要做的准备工作,那么接下来,我们一起探讨OC
和JS
相互调用的具体执行过程以及其中的要点。
JS调用OC,然后OC将处理结果返回JS
OC首先注册JS将调用的方法
OC调用registerHandler:
,这时将其调用信息存储在messageHandlers字典中以handlerName
为Key,给JS处理结果的Block
为Value(_base.messageHandlers[handlerName] = [handler copy]
);
在JS中调用被注册的方法
JS调用
1 | bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) { |
来调用上文OC注册的方法,这个brige就是上文注入JS代码时候创建的,我们再它内部做了什么。
1 | function callHandler(handlerName, data, responseCallback) { |
这里判断了参数类型,如果传入的参数只有两个,并且第二个是function
类型,那么就将第二个参数变为callBack,data置空,将handlerName和data转化成一个对象的两个属性并传给_doSend()
。
1 | function _doSend(message, responseCallback) { |
这里的responseCallback是JS先调用OC然后OC调用JS时才会有的,如果这种情况,那么需要用唯一的标识(callbackId),来将这个responseCallback存储在responseCallbacks
中,并且给message添加callbackId
这个属性。这个数值会在下次OC调用JS的时候作为唯一的Key被用到。软后将message
放入:sendMessageQueue
队列中,然后拼接src。
在回掉方法中拦截相应的方法,然后调用block
经过方法步骤2,会调用下面的回调方法
1 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{} |
在这个方法中调用
1 | NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]; |
首先获取JS中的messageQueue
(步骤2中的sendMessageQueue),然后调用flushMessageQueue:
方法:
1 | id messages = [self _deserializeMessageJSON:messageQueueString]; |
这里先将返回的JSON字符串转换成对象,这里的字符串是调用
1 | function _fetchQueue() { |
获取的,这里将sendMessageQueue
转为JSON,然后将其置空,这里为啥使用数组而不用对象来存储呢?因为可能JS还没有处理结束就有两次调用,要保证他们不丢失使用了数组。然后判断数组中的Message对象是否有responseId
(JS调用OC第一次时存储的),这里没有responseId
所以走else
:如果有callbackId
(在JS中作为回调用的),定义responseCallback
,这个block
就是OC将处理结果返回给JS时用到的block。如果没有callbackId
说明,不需要回调JS,这个时候responseCallback
为空。最后调用步骤1中存储在messageHandlers
对象中的block,并且将刚才创建的responseCallback
作为参数传入,以便OC将计算结果传递给JS。
OC将计算结果返回给JS
1 | [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) { |
在handler
的最后一步调用responseCallback()
将处理结果回调给JS。这个responseCallback()
就是我们在步骤3中创建的responseCallback
。我们再来看这个block。看步骤3可以看到这个其内部调用
[self _queueMessage:msg];
[self _dispatchMessage:message];
在_dispatchMessage
内部执行:
1 | NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON]; |
接下来JS中的_handleMessageFromObjC
就会接收到OC传过来处理结果。
1 | function _doDispatchMessageFromObjC() { |
这个时候我们看到了步骤三中的responseId
的作用了,这时候responseId
就表明了是OC将处理结果传递给JS并不需要JS再调用OC了,这时只调用responseCallback(message.responseData);
将数据传给JS。
这样我们就完成了JS调用OC,然后OC将结果回调给JS的全部过程。
OC调用JS,然后JS将处理结果返回给OC
JS注册相应的方法供回调
同OC注册方法时候一样,JS也是用一个messageHandlers
对象来存储
1 | function registerHandler(handlerName, handler) { |
OC调用JS时存储调用信息
1 | - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName { |
这里使用message
字典来存储参数,方法名,使用responseCallbacks
来存储JS处理完之后,需要回调的Block(这里为了确保多次调用不会覆盖之前的调用,使用了唯一的callbackId)。
同上文所述,最终会调用
1 | - (NSString*) _evaluateJavascript:(NSString*)javascriptCommand { |
JS调用_dispatchMessageFromObjC
这时message没有responseId
,会走else
,
1 | if (message.callbackId) { |
这里定义了需要给OC传递结果的responseCallback
,取出之前注册的handler
:messageHandlers[message.handlerName]
,然后调用这个handler
,并将这个responseCallback
作为参数传进去,handler(message.data, responseCallback);
JS将结果回传给OC
在步骤三中调用handler:
1 | function(data, responseCallback) { |
在这个handler
的结尾调用步骤三种的responseCallback
(传入的只有数据没有回调),根据步骤三可以看出来其会调用_doSend
方法。该方法中由于没有传进去回调,所以不会给message对象添加callbackId
,只调用:
1 | sendMessageQueue.push(message); |
这是由于含有responseId
(在步骤三中的_doSend
调用时设置),所以只会取出之前存储的block,并且将结果回传给OC:
1 | //调用之前存储的Bolck |
至此,OC和JS交互的所有逻辑已介绍完毕(WKWebView实现方式相同),总结下两种情景的回调,其实现方式及其相似,正如文章开头的总结。