一、WKWebView
1. 简介
WKWebView 是苹果在 WWDC2014 发布会上发布 iOS 8时,推出的 WebKit 的核心控件,是新型的 H5 容器,可以用来替代 UIWebVeiw 。它与 UIWebView 相比较,拥有更快的加载速度和性能,更低的内存占用;将 UIWebViewDelegate 和 UIWebView 重构成了14个类,3个协议,可以让开发者进行更加细致的配置。
2. WKWebView 与UIWebView 相比的优劣势
优势:
- 内存占用更低 内存占用只有 UIWebView 的1/4 左右
- 更多的支持 HTML5 的特性
- 高达 60fps 的滚动刷新率以及内置手势
- 与 Safari 相同的 JavaScript 引擎
- 通过 KVO 监听
estimatedProgress,可以获取加载进度( UIWebView 需要调用私有API) - 通过 KVO 监听 title 属性,设置导航栏标题
- 新增 WKWebViewConfiguration 类实现JS调用OC(UIWebView 使用JavaScript Core)
- 新增 customUserAgent 属性,可用来自定义UserAgent
劣势:
WKWebView 会忽视默认的网络存储,如:NSURLCache、NSHTTPCookieStorage、NSCredentialStorage。WKWebView 有自己的进程,同样也有自己的存储空间用来存储 Cookie 和 Cache, 其他的网络类如 NSURLConnection 是无法访问到的,不支持缓存。 WKWebView 对 Cookie 的管理不会同步到 NSHTTPCookieStorage,需要手动注入 Cookie,实现Cookie共享
3. WKWebView 的基本使用
WKWebView框架概览
这里只说一些比较重要的使用,具体参考: 参考链接一:WebKit框架 浅析 、参考链接二:iOS进阶之WKWebView 。
(1)一个重要的属性 WKWebViewConfiguration:
- 网页的渲染与展示,通过 WKWebViewConfiguration 可以进行配置。
- WKUserContentController、WKUserScript 、
- (void)addUserScript:(WKUserScript *)userScript网页中手动加入一段JS代码(也可以通过这种方式向 webView 注入 Cookie ) - WKWebViewConfiguration 通过使用 WKUserContentController 、WKScriptMessageHandler 协议,实现 JavaScript调用 OC (以下所有 JavaScript 都用 JS 代替)。步骤如下:
- 注册 JS 方法 :
实现 WKScriptMessageHandler 协议中的代理方法,根据message的name属性进行判断执行相应的方法[userContentController addScriptMessageHandler:self name:@"sayhello"] ;
- (void)userContentController: (WKUserContentController *) userContentController didReceiveScriptMessage:(WKScriptMessage *)message
- dealloc中移除 注册的方法:
(2)三个重要的代理:[userContentController removeScriptMessageHandlerForName:@"sayhello"];
- 注册 JS 方法 :
- WKNavigationDelegate
- 追踪加载过程
- 开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation
- 开始返回
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation
- 加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
- 加载失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
- 开始加载
- 追踪加载过程
- 进行页面跳转
- 接受重定向跳转请求后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation*)navigation
- 收到请求后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
- 发请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
- 接受重定向跳转请求后调用
- WKUIDelegate
- 创建一个新的 webview
- 创建一个新的 WKWebview
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures*)windowFeatures;
- 创建一个新的 WKWebview
- 提示框 ,针对于 web 界面的三种提示框(警告框、确认框、输入框)分别对应三种代理方法。
- 界面弹出警示框 :
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
- 界面弹出确认框 :
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
- 界面弹出输入框 :
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullableresult))completionHandler
- 注意:h5调用 js.alert() 时,UIWebView 会直接弹出提示框,WKWebview 则需要实现这些代理方法才能正常弹出提示框(不知道这个的,都以为 提示框这块是个坑)
- 界面弹出警示框 :
- 创建一个新的 webview
- WKScriptMessageHandler 协议
- JS 和 Native 交互这个协议中包含一个必须实现的方法,这个方法是 Native 与 Web 端交互的关键,它可以直接将接收到的 JS 脚本转为 OC 或Swift 对象。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
(3)WKWebView 处理与JS 交互问题:
- OC 调用 JS :
- 直接调用
这个方法通过block异步返回结果。解决了 UIWebView 使用的 stringByEvaluatingJavaScriptFromString 这个方法返回值只能是字符串,无法捕获错误的问题。- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id result, NSError * _Nullable error))completionHandler;
- 直接调用
- JS调用OC:
- 方式一:WKWebViewConfiguration 通过使用 WKUserContentController、WKScriptMessageHandler协议 在协议方法中执行OC方法。
- 方式二:H5调用js.alert()时,会调起WKWebView 的WKUIDelegate 协议下的代理方法显示提示框。
- 方式三:使用第三方框架WKWebViewJavascriptBridge 完成JS与Native之间的交互。
4.WKWebView 的Cookie相关
的困扰, 我们只需要写一个方法, 往 HTTPCookieStorage里面注入一个我们用户的 HTTPCookie 就可以了。同一个应用,不同 UIWebView 之间的 Cookie 是自动同步的。并且可以被其他网络类访问比如NSURLConnection、AFNetworking。 由于WKWebView会忽视默认的网络存储,如: NSURLCache、 NSHTTPCookieStorage、NSCredentialStorage。无法实现,不会主动把 Cookie 存储的NSHTTPCookieStorage,网络类无法访问 WKWebView 自己管理的 Cookie,也无法从 NSHTTPCookieStorage 中取到 WKWebView 请求时用到 Cookie。所以在WKWebView需要发起网路请求时,需要我们手动去注入 Cookie。大概思路:将cookie信息存入 NSHTTPCookieStorage,然后在适当的时机从NSHTTPCookieStorage 获取 Cookie 注入 WKWebView 。下面就带着这样几个问题具体说一下:用户端现在的注入 Cookie 方案是什么?解决了什么样的问题?既然不能共享在 NSHTTPCookieStorage 为什么还需要使用 NSHTTPCookieStorage 存取 Cookie?- 用户端现在的注入 Cookie 方案是什么?解决了什么样的问题?
- 方案一:在 WKWebView 发起网络请求向请求头中注入 Cookie ,解决了首次请求 Cookie 获取不到的问题
- 方案二:在 WKWebView 的WKNavigationDelegate 代理方法 decidePolicyForNavigationAction 中 拦截网络请求向请求头中注入 Cookie 注入cookie ,解决了打开新的页面 Cookie 带不过去的问题
- 方案三:在 WKWebView 的 WKNavigationDelegate 代理方法 decidePolicyForNavigationAction 中 拦截网络请求注入 Cookie,解决了打开新的页面 Cookie 内容发生改变,返回上级页面 Cookie 未更新的问题
- 小结:经测试方案三页可以同时解决方案二能解决的问题,因此用户端现在的 Cookie 注入方案是同时了方案一与方案三
- 参考链接:参考链接一:iOS中UIWebView与WKWebView、JavaScript与OC交互、Cookie管理看我就够、 参考链接二:【腾讯Bugly干货分享】WKWebView 那些坑
- 既然不能共享在NSHTTPCookieStorage为什么还需要使用NSHTTPCookieStorage存取cookie?
- 苹果提供了NSHTTPCookieStorage、NSHTTPCookie这两个类是用来存储Cookie。
- cookie信息不仅限于WKWebView的网络请求使用,某些native页面的网络请求也需要cookie信息,并且网络类
NSURLConnection、AFNetworking 等是通过访问 NSHTTPCookieStorage 获取 Cookie 信息。 如果 Cookie 信息,使用其他方式存储,我们不仅要管理 WKWebView 的 Cookie ,还要管理非 WKWebView 的网络请求能否获取到 Cookie。
二. WKWebViewJavascriptBridge 的使用
大概原理:WebViewJavascriptBridge 的实现过程就是在 OC 环境和 JS 环境各自保存一个相互调用的信息(可以理解为方法名和方法的实现)。每一个调用之间都有 Id 和 CallBackId 来找到两个环境对应的处理。
1.WebViewJavascriptBridge框架重要的类:
- WebViewJavascriptBridge_JS: JS 环境的 bridge 初始化和处理,里面负责接收 OC 发给 JS 的消息,并且把 JS 环境的消息发送给OC。
- WKWebViewJavascriptBridge:主要负责 OC 环境的消息处理,并且把 OC 环境的消息发送给 JS 环境。
WebViewJavascriptBridgeBase:主要实现了 OC 环境的 bridge 初始化和以及与 JS 交互的消息的封装等重要逻辑处理。
2.OC环境的消息处理流程:
(1) WKWebViewJavascriptBridge 打开日志方便调试
(2) 初始化bridge,大概做了几件事:
- 初始化 bridge 对象
- 把 webView 作为成员变量,成为 navigationDelegate
- 初始化 WebViewJavascriptBridgeBase 对象,成为 delegate 用于处理交互的传递等逻辑
(3) OC环境注册方法,供 JS 调用
- 其实是向 WebViewJavascriptBridgeBase 注册,WebViewJavascriptBridgeBase 对象将这个方法 以 “方法名:方法的实现”的键值对的形式存入messagehandlers,在收到JS端发送的消息时执行 navigationDelegate 方法时从 messageHander 获取方法进行执行。
(4) 初始化Web环境(这个不需要我们做)
(5) OC 向 JS 发消息 调用 JS 注册的方法
(6) 当 JS 向 WebView 发送消息的时候回调用这个代理方法,拦截到的URL判断URL类型做出相应的处理。到这就是整个 OC 和 JS 交互在 OC 环境下的基本流程啦,具体参见:参考链接一:GitHub WebViewJavascriptBridge框架下载链接、 参考链接二:WebViewJavascriptBridge原理解析 、参考链接三:Objective-C 和 JavaScript 交互那些事
代理方法: - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler- 拦截到的URL类型相关判别方法
方法 | 作用 |
|---|---|
-(BOOL)isCorrectProcotocolScheme:(NSURL*)url | 判断是否是WKWebViewJavascriptBridge接收或者是发送的消息URL |
-(BOOL)isBridgeLoadedURL:(NSURL*)url | 是否是 初始化JavaScript环境的bridge消息 |
-(BOOL)isQueueMessageURL:(NSURL*)url | 是否是WKWebViewJavascriptBridge发出的消息 |
-(void)logUnkownMessage:(NSURL*)url | 未知消息类型 |
- 处理逻辑:是否是 WKWebViewJavascriptBridge 接收或者是发送的消息URL [_base isCorrectProcotocolScheme:url]
- 是 :首先是否是初始化 Web 环境 bridge 的初始化加载消息----> https://__bridge_loaded__ [_base isBridgeLoadedURL:url]
- 是: 初始化 JS环境的 bridge。
- 否:再判断是否是WKWebViewJavascriptBridge发出的消息 [_base isQueueMessageURL:url]
- 是 调用
[self WKFlushMessageQueue];方法。然后通过调用WebViewJavascriptBridge._fetchQueue()来获取 JS 给 OC的回调信息。拿到回调消息后,调用[_base flushMessageQueue:result] 方法从 OC 发送给 OC 。 - 否 调用 [_base logUnkownMessage:url] 打印未知的消息。
- 是 调用
- 否 :不是JS交互请求,按照正常的网络请求处理。
3.WKWebViewJavascriptBridge 的坑:
由于源码中对_webViewDelegate进行了强引用关键代码如下图,所以使用 WKWebViewJavascriptBridge 必然会导致循环引用。
原来的解决方案:
这个解决方案是 WKWebViewJavaScriptBridge循环引用最简单处理办法 。但是这么处理会导致一些问题,在网路请求未拿到数据后进入其他页面,待数据加载到后回到之前页面,代理方法失效。解决方法 通过控制器的 isBeingDismiss 这个方法判断控制器来源,当控制器将要销毁时,打破循环引用。如下图:
关于 WKWebView 缓存相关的总结可以通过下一篇博客 WKWebView缓存总结 了解。
三、参考链接汇总
- WKWebView原理使用相关:
- WKWebView Cookie 相关:
- WebViewJavascriptBridge原理相关: