UIWebview使用jsContext获取上下文失效

JavaScriptCore在实际项目中的使用的坑

因为我的项目支持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,还有其它的方法。

解决方案
http://stackoverflow.com/questions/18920536/why-use-javascriptcore-in-ios7-if-it-cant-access-a-uiwebviews-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对象

你可能感兴趣的:(UIWebview使用jsContext获取上下文失效)