备注:转载请说明出处http://blog.csdn.net/u011296699/article/details/50435559
JavaScriptCore在实际项目中的使用的坑
前言
写这篇文章目的在于备份自己解决这个坑的过程,便于以后忘了可以随时查看。
当然如果文章中有说的不正确的观点,欢迎交流,欢迎拍砖。
在刚开始使用JavaScriptCore与JS通信的时候,在网上搜了一推资料,这篇文章讲的不错,推荐看看UIWebView 中JavaScript 与 Objective-C 通信
文章里说了JS与OC在各个ios系统版本通信的机制
因为我的项目支持ios7+,我采用了JavaScriptCore框架实现的OC与JS的通信
先说说我在实际项目开发中遇到的坑。具体问题可以看这篇文章的说明:使用javascriptcore 框架后,UIWebView中页面跳转后,用JSExport绑定的方法无法调用
对遇到的技术问题我的解题思路是:
- 了解原理--分析原因--对症下药
在分析原因之前我们先了解下浏览器加载渲染网页的过程
- 1.用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;
- 2.浏览器开始载入html代码,发现
<head>
标签内有一个<link>
标签引用外部CSS文件; - 3.浏览器又发出CSS文件的请求,服务器返回这个CSS文件;
- 4.浏览器继续载入html中
<body>
部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了; - 5.浏览器在代码中发现一个
<img>
标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码; - 6.服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
- 7.浏览器发现了一个包含一行Javascript代码的
<script>
标签,赶快运行它; - 8.Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个
<div> (style.display=”none”)
。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码; - 9.终于等到了
</html>
的到来,浏览器泪流满面…… - 10.等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下
<link>
标签的CSS路径; - 11.浏览器召集了在座的各位
<div><span><ul><li>
们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。
UIWebView三个代理被调用的时机
- (BOOL)webView:(UIWebView )webView shouldStartLoadWithRequest:(NSURLRequest )request navigationType:(UIWebViewNavigationType)navigationType
- (void)webViewDidStartLoad:(UIWebView *)webView
- (void)webViewDidFinishLoad:(UIWebView *)webView
根据官网的文档说明
- shouldStartLoadWithRequest:Sent before a web view begins loading a frame
- webViewDidStartLoad:Sent after a web view starts loading a frame.
- webViewDidFinishLoad:Sent after a web view finishes loading a frame
对浏览器(UIWebView)加载和渲染Html有了感性认识后,我们再了解下JavaScriptCore框架是怎么把OC的对象暴露让JS调用的。我的理解是通过JSExport协议把对象注册为暴露对象,JavaScriptCore框架把这个暴露对象转化成JS环境的对象,等着在OC获取到web的JS上下文后,把转化好的OC对象注入到JS上下文(也就是JavaScriptContent),感觉有点类似往web的DOM里面注入JS一样。通常的做法是通过KVC
获取到UIWebView的javaScriptContext,再往javaScriptContext里注入OC对象(由JavaSciptCore转换OC的对象)。
分析原因
了解了原理,我们来看下我遇到的坑的原因。当在html进行页面跳转的时候,JS调用OC对象出现undefined,很明显就是在JS调用OC对象时,还没有注入成功。
那么问题来了!
- 第一:我们应该在什么时候正确的注入OC对象?
- 第二:获取javaScriptContext的方法真的就只能是通过KVC获取吗?
我们先来看看第一个问题,在网上一搜,基本上都是说在
- (void)webViewDidFinishLoad:(UIWebView *)webView
方法里注入OC对象
jsContext[@”nativeApis”] = nativeApis;
为什么?据网上的说法是因为如果不在webViewDidFinishLoad这个方法里面注入的话,注入的对象很有可能会被销毁。
我们试试这个办法,很是奇怪,为什么在html跳转的时候就不行呢,我们在webViewDidFinishLoad方法里往JavaScriptContext注入OC对象,应该是注入成功了啊!!怎么在html跳转的时候,调用OC的对象就不存在呢?
分析原因,原来是webViewDidFinishLoad这个方法是在web的window.onload以后才调用(也就是html所有的资源已经加载和渲染结束后),这就明了了,在JS调用OC的对象时,还没有注入。
对这个问题我的理解是:应该在UIWebView创建好javaScripContext后注入。在UIWebView解析渲染Html标签之前,注入OC的对象,那么第一个问题就解决了。
ok,沿着这个思路分析下去,只要我们找到了在javaScriptContext创建成功后就注入OC的办法,这个坑应该就可以搞定了。
但是UIWebView并没有给我们webView:didCreateJavaScriptContext:forFrame:
相关的代理接口,让我们在JavaScriptContext创建好后,返回给我们JSContext。现在获取JSContext的方法貌似网上都是说的通过KVC来获取。但是在UIWebViewDelegate的三个代理接口获取jsContext注入native对象时机又不对,这怎么解决?
带着没有解决不了的问题的心里,在网上找了一推资料,最后还是在强大的StackoverFlow找到了答案。
获取UIWebView的JSContext,还有其它的方法。
解决方案
看了这个文章,我遇到这个坑解决了:Why use JavaScriptCore in iOS7 if it can’t access a UIWebView’s runtime?
我们来看看他实现的原理:
通过获取App中创建的所有的UIWebView,使用UIWebView的stringByEvaluatingJavaScriptFromString
方法,以UIWebView的hash值生成一个字符,往UIWebView的JavaScriptContext里设置一个标识。在等着监听到JavaScriptContext创建好后,获取当前JavaScriptContext中的这个标识和当前的UIWebView的hash值是否一致,如果一致当前的JavaScriptContext就是和当前的UIWebView是一致的,再把这个消息回调给相应的委托者,在- (void)webView:(UIWebView *)webView didCreateJavaScriptContext:(JSContext*) ctx
这个代理回调方法里面把实现了JSExport协议的对象注入到JSContext里面。
到此我们总结下JS与OC通信在实际运用中的关键点:
- 1、有效的获取当前UIWebView加载的内容的javaScriptContext
- 2、在UIWebView开始解析html之前(或说JavaScriptContext已经创建好后)注入Native对象