iOS8之后,Apple推出了最新的WKWebView,经历了若干代的发展之后,WKWebView日趋完善,在目前的开发项目当中也得到了充分的使用。
为什么使用WKWebView?
因为老旧的UIWebView存在严重的性能和内存消耗问题,限制了业务的自由度。WKWebView 采用跨进程方案,Nitro JS 解析器,高达 60fps 的刷新率,理论上性能和 Safari 比肩,而且对 H5 的高度支持。
由于WKWebView使用跨进程方案,不会增加app的使用内存,所以保证了比较好的性能和体验。如图所示,进程分布。
UIWebView和WKWebView的流程区别
如图所示,WKWebView的流程粒度更加细,不但在请求的时候会询问WKWebView是否请求数据,还会在返回数据之后询问WKWebView是否加载数据。
#请求数据的时候询问
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
#返回数据的时候询问
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
在流程中,WKWebView返回的错误粒度也比UIWebView细,如代码所示:
#请求数据时发生的error
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
#请求之后加载H5发生的error
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
深入JavaScript与Native
JavaScript与Native之间的交互一直是Web与Native的重要行为,在WKWebView中,JavaScript与Native的交互还是很优雅的。
Native调用JavaScript
#pragma mark - UIWebView
NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
#pragma mark - WKWebView
[wkWebView evaluateJavaScript:@"document.title"
completionHandler:^(id _Nullable ret, NSError * _Nullable error) {
NSString *title = ret;
}];
WKWebView 提供的接口和 UIWebView 命名上较为类似,区别是 WKWebView 的这个接口是异步的,而 UIWebView 是同步接口
JavaScript调用Native
WKWebView 绑定共享对象,是通过特定的构造方法实现,参考代码,通过指定 UserContentController 对象的 ScriptMessageHandler 经过 Configuration 参数构造时传入。
WKUserContentController *userContent = [[WKUserContentController alloc] init];
[userContent addScriptMessageHandler:id<WKScriptMessageHandler> name:@"MyNative"];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = userContent;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
通过addScriptMessageHandler:name:指代实现WKScriptMessageHandler协议的对象,以及被js调用的方法名称,结束是需要移除。
而handler 对象需要实现指定协议,实现指定的协议方法,当 JS 端通过 window.webkit.messageHandlers 发送 Native 消息时,handler 对象的协议方法被调用,通过协议方法的相关参数传值。
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
WKWebView的痛点
Cookie问题
这个坑自iOS 8之后一直被Apple公司做出各种调整甚至到了iOS 10和iOS 11版本在Cookie上依然做出了比较大的调整,在传统的UIWebView中,Cookie是通过NSHTTPCookieStorage统一管理,服务器返回时写入,发起请求时服务,Web和Native能共享Cookie。在WKWebView中,Cookie的写入不是实时的,而且请求时也不会实时读取Cookie,这导致了app会丢失cookie,无法做到session和Native同步。
跨域问题
跨域问题,HTTPS 对 HTTPS、HTTP 对 HTTP 跨域默认是能载入的,但如果是 HTTP 想载入 HTTPS 跨域链接,因为安全考虑,WKWebView 会被拦截,这问题在引入跨域 HTTPS 的页面也做 HTTPS。
关于WKWebView在iOS11上的新特性
Cookie的管理
iOS11上,WKWebView 新增了 Cookie 管理 API WKHTTPCookieStore,通过该接口可以设置、删除和查询 WKWebView cookie,甚至可以监听 cookie store 的变化。(但是对于UIWebView和WKWebView混合使用的业务,需要对WKHTTPCookieStore和NSHTTPCookieStorage之间做一些同步操作。)
加载本地资源
由于WKWebView的网络请求是在非主进程中发起,所以NSURLProtocol无法拦截请求,在iOS11中,提供了专门的接口加载本地资源。
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id)urlSchemeTask
但是,依然有坑,WKWebView不允许拦截 Scheme 为 “http”、“https”、“ftp”、“file” ,我想这一目的是为了限制开发人员只能下载属于自己的资源而做出的限制,也是出于一种安全的考虑。
Filter Unwanted
Filter unwanted contentWWDC 2015上,WebKit 团队介绍了 Safari 上新增的 Content Blocker 特性,可以实现阻止页面内容加载或隐藏页面内容等功能。在WWDC 2017上,WebKit 团队将这一特性移植到了 WKWebView。