WKWebView 小记
WKWebView 早在 iOS 8 中已经推出,至今已经过去很久了,但是由于项目时间过久,代码比较老,所以一直停留在 UIWebView,没有尝试用过 WK。这次有个机会改造,在这里记录一下遇到的一些小问题。
关于 Cookie
在使用 UIWebView 时,我们从来不需要考虑 Cookie 的问题。但是 WKWebView 不会将 Cookie 存入到标准的 Cookie 容器 NSHTTPCookieStorage 中,也不会自动读取,所以使用时需要我们手动去管理 Cookie。而由于情况繁多,WKWebView 的 Cookie 问题变成很多人替换 UIWebView 的一个犹豫的原因。
目前我的解决方案为:
- WKWebView loadRequest 前,在 request header 中设置 Cookie;
1 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.urlString ? : @""]]; |
1 | + (NSString *)allCookieString { |
- 通过 document.cookie 设置 Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题;
1 | - (void)setupDocumentCookie { |
1 | + (NSString *)javascriptStringWithCookie:(NSHTTPCookie*)cookie { |
在阅读别人的博客时,观察到 document.cookie 的设置没有处理 Cookie 的 domain 和 path,我在测试时发现这样 Cookie 无法设置成功,所以这里添加了 Cookie 所有的信息。
- 即便如此,防止有些情况仍然请求中无法携带 Cookie,在每次 Web 开始加载时判断,并且在需要时添加 Cookie;
1 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { |
上述处理之后,目前线上版本关于 Cookie 的问题暂时没有发现。
关于请求拦截
客户端与 H5 交互的常用手段之一,便是拦截请求,根据 scheme 进行区分和不同的处理。这也是目前项目中运用较多的手段。WKWebView 的拦截处理方式与 UIWebView 并没有什么不同,只是用另外的的代理函数处理。
1 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler; |
注意这个代理函数必须手动调用 decisionHandler 这个 block 来说明是否允许此次跳转。
关于 openURL
测试时发现,在 WKWebView 中,URL Scheme and App Store links 不会自动跳转,因此需要拦截并且手动处理此类跳转。
1 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler |
关于 WKScriptMessageHandler 协议的循环引用
在 WKWebView 中,使用 WKUserContentController 便可实现与 H5 的交互。只是这个方式会导致循环引用,使 VC 无法 dealloc。
1 | - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name |
在这里我们新增一个中间处理层来处理这种情况。
新建 WeakScriptMessageHandler 实现协议 WKScriptMessageHandler,并且暴露对外的一个代理,此代理同样需要实现 WKScriptMessageHandler 协议;
1 | @interface LCSWeakScriptMessageHandler : NSObject <WKScriptMessageHandler> |
在 .m 中,代理方法调用时,再调用真正的代理
1 | @implementation LCSWeakScriptMessageHandler |
webView 使用时,只需要将 Handler 设置好并且正常实现 WKScriptMessageHandler 即可。
1 | - (WKUserContentController *)userContentController { |
注意在 dealloc 时需要调用相应的 - (void)removeScriptMessageHandlerForName:(NSString )name; 方法。*
关于 webView.estimatedProgress
在 WKWebView 中,新增了一个属性 webView.estimatedProgress(0~1) 使我们可以通过它来设置一个 webView 的加载进度条,但是这只是一个估算的进度,并不十分准确,实践发现会出现最终值不为 1 的情况。
如果只是导航栏下方的自定义进度条并无大碍,最多可以适时手动隐藏,但是如果是想添加占位图就不是那么合适。为了较为准确的掌握网页的加载进度,我尝试了使用 document.readyState 这个状态来控制,但是实际运用时发现并不理想。
1 | [webView evaluateJavaScript:@"document.readyState" completionHandler:^(id _Nullable result, NSError * _Nullable error) { |
最终,我还是通过了一种非常质朴的方式🌚,拦截与 H5 约定好的一个type,在上述拦截代理方法里进行处理,这样虽然达不到占位图消失与内容显示完美吻合,但目前使用起来没有太大问题。
关于 webView.scrollView 的代理
由于 WKWebView 的滚动速率较慢,使用时需要需要通过 scrollView.delegate 调整滚动速率:
1 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { |
然而这样设置 scrollView.delegate 为自身后,在 iOS 9 系统版本的机器上,会在 dealloc 方法中创建弱引用对象,从而导致崩溃。
所以在使用时,需要在 dealloc 方法里设置好:
1 | _webView.scrollView.delegate = nil; |
关于白屏问题
WKWebView 占用内存过大时,不会导致系统 crash,但是WebContent Process 会 crash,从而出现白屏现象,详情原因可以看 腾讯Bugly 的这篇博客。
我在测试时没有发现这种情况,但是为了安全起见,仍然采取了相应的措施来处理这种情况。
- 在 WKWebView 总体内存占用过大,页面即将白屏的时候,系统会调用代理中的这个函数
1 | - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0)); |
在这里可以通过 reload webView 来防止白屏出现;
- 在WKWebView白屏的时候,另一种现象是 webView.titile 会被置空, 因此,可以在 viewWillAppear 的时候检测 webView.title 是否为空来 reload 页面。
总结
WKWebView 相比 UIWebView 而言可能改变较多,有一些坑,需要我们去多了解之后再去应用。但其在内存消耗以及稳定性方面有很大优势,对其应采取积极的态度,有机会可以替换体会一番。
参考链接: