数据交互的途径
- 拦截URL
MessageHandler
- 注入JS代码
-
evalueJavascript
函数 - cookie
- 拦截http请求
拦截URL
实现关键: WKNavigationDelegate
.
通过实现WKNavigationDelegate
的回调函数webView(_:decidePolicyFor:decisionHandler:)
来拦截Web页面跳转的URL, Web端可以把数据放在URL中.
MessageHandler
实现关键: WKScriptMessageHandler
, WKWebViewConfiguration
.
通过WKWebViewConfiguration
, 向webView添加实现了WKScriptMessageHandler
协议的对象, 监听来自JS端的调用.
必须在WKWebView
初始化的时候完成添加.
let config = WKWebViewConfiguration()
// someHandler是实现了WKScriptMessageHandler的实例化对象
config.userContentController.add(someHandler, name: "nameOfSomeHandler")
let web = WKWebView(frame: .zero, configuration: config)
注入JS代码
实现关键: WKUserScript
, WKWebViewConfiguration
.
用WKUserScript
包装脚本代码, 通过WKWebViewConfiguration
, 向webView添加可供Web端执行的代码.
let config = WKWebViewConfiguration()
// someScriptString是脚本代码字符串
let someScriptString = "..."
config.userContentController.addUserScript(WKUserScript(source: someScriptString, injectionTime: .atDocumentStart, forMainFrameOnly: true))
let web = WKWebView(frame: .zero, configuration: config)
evalueJavascript
通过WKWebView
提供的函数evalueJavascript(_:completionHandler:)
, 从Native端调用Web端的JS函数.
/*
@abstract Evaluates the given JavaScript string.
@param javaScriptString The JavaScript string to evaluate.
@param completionHandler A block to invoke when script evaluation completes or fails.
@discussion The completionHandler is passed the result of the script evaluation or an error.
*/
open func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
cookie
iOS 中 cookie 有专门的类型HTTPCookie
, iOS11 以前WKWebView
的 cookie 和 app 共享, 在 iOS11 以后WKWebView
的 cookie 会单独自己管理.
获取cookie
在WKNavigationDelegate
的回调webView(_:decidePolicyFor:decisionHandler:)
参数中, 有一个WKNavigationResponse
对象, 有几种方式可以从中取出cookie:
-
HTTPCookie
的静态函数cookies(withResponseHeaderFields:for:)
-
HTTPCookieStorage
(iOS 11以前和 WebKit 共享, iOS11以后WebKit 不包含在内) -
WKHTTPCookieStore
(iOS 11以后)
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if
let response = navigationResponse.response as? HTTPURLResponse,
let headers = response.allHeaderFields as? [String : String],
let url = response.url
{
// 1.通过HTTPCookie获取
HTTPCookie.cookies(withResponseHeaderFields: headers, for: url) //
// 2.通过HTTPCookieStorage获取
HTTPCookieStorage.shared.cookies(for: url) // 获取对应此 URL 的 cookie
HTTPCookieStorage.shared.cookies // 获取全部 cookie
}
// 3.通过WKHTTPCookieStore获取
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { (cookies) in
}
decisionHandler(.allow)
}
设置 cookie
可以通过以下方式:
-
HTTPCookieStorage
(iOS 11以前) -
WKHTTPCookieStore
(iOS 11以后)
let url = URL(string: "https://www.jianshu.com")!
if let cookie = HTTPCookie(properties: [HTTPCookiePropertyKey.commentURL: url]) {
if #available(iOS 11.0, *) {
// 2.WKHTTPCookieStore设置
webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie, completionHandler: nil)
}else {
// 1.HTTPCookieStorage设置
HTTPCookieStorage.shared.setCookie(cookie)
HTTPCookieStorage.shared.setCookies([cookie], for: url, mainDocumentURL: url) // 单独针对某 URL 设置
}
}
补充: UIWebView
和 WKWebView
不一样, 它是通过HTTPCookieStorage
和 APP 以及系统共享 cookie 的.
拦截自定义http请求
iOS11以后可以使用WKURLSchemeHandler
协议拦截指定scheme的http请求.
在iOS13以前可以通过私有API拦截scheme为http/https
, iOS13后无法再访问私有变量, 因而无法实现此类请求的拦截.
关键: WKWebViewConfiguration
, WKURLSchemeHandler
, WKURLSchemeTask
.
- 自定义一个实现了
WKURLSchemeHandler
协议的类型, 在回调中处理对应scheme的请求.
class SomeURLSchemeHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
// 可以自己 直接创建 URLResponse 做回调, 或者另外调用网络请求获得 URLResponse
...
}
// 任务被告知要停止
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
// 可以在此处把 webView(_:start:) 处发起的异步任务停掉
...
}
}
- 自定义类需要在回调
webView(_:start:)
中操作WKURLSchemeTask
对象, 根据情况调用对应的函数:
/*
如果任务已经被停止, 调用以下函数就会抛出异常
*/
/*
把自定义的响应传递过去, 必须调用至少1次.
如果任务已经标记完成后再调用, 会抛出异常.
*/
func didReceive(_ response: URLResponse)
/*
把响应数据传递进去, 必须在didReceive(_ response: URLResponse)之后调用
如果任务已经标记完成后再调用, 会抛出异常.
*/
func didReceive(_ data: Data)
/*
在响应成功后调用
如果任务已经标记完成或者失败后再调用, 会抛出异常
*/
func didFinish()
/*
在响应出错后调用
如果任务已经标记完成或者失败后再调用, 会抛出异常
*/
func didFailWithError(_ error: Error)
- 在
WKWebView
初始化的时候, 把自定义请求拦截配置好.
let config = WKWebViewConfiguration()
config.setURLSchemeHandler(someURLSchemeHandler, forURLScheme: "someScheme")
let webView = WKWebView(frame: .zero, configuration: config)
小结
-
MessageHandler
/JS 代码注入/请求拦截, 这3个方案都需要依赖WKWebViewConfiguration
, 在WebView
初始化的时候就要完成配置. - iOS13后, 无法通过私有API拦截
http/https
scheme 的请求, 所以 hybrid 的时候很难做到对 web 端无入侵. - 为了做加载优化(资源, 数据), 可以使用自定义 scheme 的请求, 或者 web 端调用
MessageHandler
告知 native 端, 这需要两端协商加载的方案. - iOS11以后,
WKWebView
的cookie不再是共享的, 需要单独针对维护. - 获取
WKWebView
的cookie可以从WKNavigationResponse
中获取.