iOS8 新特性 WKWebView
WKWebView 相对于 UIWebView 有以下几点显著的提升:
- 使用 Nitro JavaScript 引擎,加载处理 JS 代码的速度明显提升四倍左右。
- 对 Web APP 加载的速度提升百分之20左右。
- 优化了 WebView 的内存使用。
- 开放了更多的 API.
这已经足以让我们考虑在支持 iOS8 设备的应用中使用 WKWebView.
和 UIWebView 共存
现阶段完全不兼容 iOS7 还为时过早,但是又想在项目中使用功能更多、效率更高的 WKWebView, 那么必须有一套方案来解决 WKWebView 和 UIWebView 的共存问题。也就是说,在 iOS7 及以下版本仍使用 UIWebView, 在 iOS8 以上使用 WKWebView. 上文提到两种 WebView 的 API 并不完全相同,在使用相同功能的接口时,要判断当前 WebView 是哪种实例才能分别调用各自的接口。这样代码中会有多处相似的逻辑判断。
针对这个问题,下文提出一种方案,封装一个统一的 WebView 来解决不同 iOS 版本的兼容问题。
第一步:定义抽象 Protocol
首先总结出经常要使用的一组属性和接口,定义一个抽象 protocol, 在 protocol 中统一定义这些属性或者接口,遵从该 protocol 的不同类再去具体实现这些接口,即可完成 API 的统一。对比 UIWebView 的属性,WKWebView 中没有 request 属性,而多出 URL 、 title 等属性,这些属性都可以统一定义到 protocol 中。对 API 的统一也是使用相同的思路。
第二步:使用 Category
用 Category 扩展两个 WebView, Category 遵从第一步定义的 protocol 并分别实现 protocol 中的方法。为了在 Category 中为类动态添加属性,要使用 Runtime 的 objc_getAssociatedObject
和 objc_setAssociatedObject
方法。例如为 WKWebView 添加 request 属性,要用到如下代码:
1 | - (NSURLRequest *)request { |
虽然添加了 request 属性,但它默认没有赋值,如果想在调用loadRequest
时自动将 request 赋对应的值,那么还要使用 Method Swizzling 的方法换掉原生的loadRequest
的实现,在loadRequest
之前先进行赋值:
1 | - (void)altLoadRequest:(NSURLRequest *)request { |
第三步:封装统一的 WebView
创建一个继承自 UIView 的 WebView, 取名为 BXWebView, BXWebView 中包含一个真正的 WebView 实例 contentView,在初始化首次创建 contentView 时,iOS8 以上实例化为 WKWebView, 否则就实例化为 UIWebView. 该实例还要遵从上文定义的 protocol 以达到统一 API 的目的。封装好的 BXWebView 类的实例应该是它其中 contenView (即 UIWebView 或 WKWebView 实例) 的代理。具体的类声明如下:
1 | @interface BXWebView : UIView <UIWebViewDelegate, WKNavigationDelegate, WKUIDelegate> |
在 BXWebView 类的实现中,实例化 contenView 时需要判断 iOS SDK 中是否有 WKWebView 这个类来分别实例化对应的 WebView. 如下代码所示:
1 | if (NSClassFromString(@"WKWebView")) { |
把 contenView 的代理设置为 self (即封装的 WebView )后,构成了 ViewController ->封装好的WebView -> UIWebView/WKWebView 两级代理的层次,触发代理方法的消息分级传递下去,处理方式如下:
1 | - (void)webViewDidFinishLoad:(UIWebView *)webView { |
此外还需要根据特定业务来封装一个 WebViewController 来处理业务逻辑。至此一套 WebView 的兼容框架就成型了。