OC与JavaScript交互刷新页面失效的问题

1、先参考 Objective-C与JavaScript交互的两种方式

2、这里面提到的一个关键性问题:当JS注入到UIWebView的JSContext中后,当前页面跳转到了一个新页面后,JS注入就失效了;

3、前面提到的解决方案是在UIWebView的代理方法中重新注入,如webViewDidStartLoad或webViewDidFinishLoad方法;

4、但有一个问题,如果在webViewDidFinishLoad方法中才注入,如果页面加载过程中有JS需要调用原生方法怎么办?就会造成调用失败,另外如果在webViewDidStartLoad方法中就调用,有可能当前页面还没跳转到下一个页面(毕竟页面跳转是需要时间的),造成JS注入在当前页面,新跳转的页面还是没有被注入,这样一来怎么写都不太妥当,并且UIWebView也就那么几个代理方法,如何破?!

5、首先对JSContext这个东西做一个说明,JSContext是每一个页面的JavaScript运行环境,这个环境是针对当个页面有效并且独立存在的,并不是一个UIWebView共用一个JSContext,而是每个页面(为了帮助理解或者说每个url不知道是否合适)都拥有一个单独的JSContext,前面我们都是通过KVC的模式通过键documentView.webView.mainFrame.javaScriptContext来获取,这种方式本身问题不大(不排除上苹果商店审核不通过的可能性),关键问题在于何时调用?

6、根据代理设计模式的思路去想,最好的办法就是在UIWebView创建了一个新的JSContext时通过代理方法调用注入方法,这样最靠谱!那现在最重要的就是这个代理方法有吗?怎么去找到这个点?UIWebView是没有的,并且UIWebView的父类是UIView也是没什么希望的。

7、在OS X中发现,WebFrameLoadDelegate负责WebKit与NSWebView的通信,由于NSWebView内部仍然使用WebKit渲染引擎,若要侦听渲染过程中的一系列事件,则必须使用WebFrameLoadDelegate对象:

1
2
3
4
5
6
7
8
9
1、加载过程:
 
在一个访问一个网页的的整个过程,包括开始加载,加载标题,加载结束等。webkit都会发送相应的消息WebFrameLoadDelegate
 
webView:didStartProvisionalLoadForFrame:开始加载,在这里获取加载的url
 
webView:didReceiveTitle:forFrame:获取到网页标题
 
webView:didFinishLoadForFrame:页面加载完成
1
2
3
4
5
6
7
2、错误的处理:
 
加载的过程当中,有可能会发生错误。错误的消息也会发送给WebFrameLoadDelegate 。我们可以在这两个函数里面对错误信息进行处理
 
webView:didFailProvisionalLoadWithError:forFrame: 这个错误发生在请求数据之前,最常见是发生在无效的URL或者网络断开无法发送请求
 
webView:didFailLoadWithError:forFrame: 这个错误发生在请求数据之后

8、可是在iOS中呢?我尝试过,并没有WebFrameLoadDelegate这个对象,看来iOS中的WebKit框架并未提供UIWebView这么多的接口,但是有些人通过WebKit的源码还是发现了一二,他就是Nick Hodapp。

9、在iOS中,尽管没有暴露WebFrameLoadDelegate,但是在具体实现上仍会判断WebKit的implement有没有实现这个协议的某些方法,如果实现则仍会执行,而且在webit的WebFrameLoaderClient.mm文件中

1
2
3
4
if (implementations->didCreateJavaScriptContextForFrameFunc) {
    CallFrameLoadDelegate(implementations->didCreateJavaScriptContextForFrameFunc, webView, @selector(webView:didCreateJavaScriptContext:forFrame:),
        script.javaScriptContext(), m_webFrame.get());
}

判断当前的对象有没有实现webView:didCreateJavaScriptContext:forFrame:方法,有则执行。该方法会传递三个参数,第一个是与webkit通信的WebView(此WebView并不是UIWebVIew,Nick层做过测试通过获取的WebView并不能遍历到我们需要的UIWebVIew,因此推测,这个WebView是一个UIView的proxy对象,不是UIView类);第二个则是我们想要获取的JSContext;第三个参数是webkit框架中的WebFrame对象,与我们的期望无关。

10、为了让webkit执行这个函数,我们必须让对象实现这个方法。由于所有的OC对象都继承自NSObject对象,因此我们可以在NSObject对象上实现该方法,这样可以保证该段代码可以在webkit框架中执行。

11、写一个NSObject的Catagory,并实现该方法,通过通知的方式将对象传播出去如:

1
2
3
4
- (void)webView:(id)webView didCreateJavaScriptContext:(id)ctx forFrame:(id)frame
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SAWebViewDidCreateJavaScriptContext" object:ctx];
}

12、参考链接:https://segmentfault.com/a/1190000004285316

Leave a Reply