在我们的日常开发过程中有时需要我们对网络做拦截操作,诸如对request 的header 做修改 添加等。
对于有效的url(http 或者 https) 只需要对 request 做添加头文件即可
//这里是对资源加入的防盗链
var request = URLRequest(url:url as URL);
request.setValue(kReferer, forHTTPHeaderField: "Referer")
theWebView?.load(request)
但是有时加载只是一些本地的静态html文件,这时我们就需要对 网络做拦截
import UIKit
class WKURLProtocol: URLProtocol , URLSessionDataDelegate, URLSessionTaskDelegate{
//URLSession数据请求任务
var dataTask:URLSessionDataTask?
//url请求响应
var urlResponse: URLResponse?
//url请求获取到的数据
var receivedData: NSMutableData?
//判断这个 protocol 是否可以处理传入的 request
override class func canInit(with request: URLRequest) -> Bool {
//对于已处理过的请求则跳过,避免无限循环标签问题
if URLProtocol.property(forKey: "WKURLProtocolHandledKey", in: request) != nil {
return false
}
return true
}
//回规范化的请求(通常只要返回原来的请求就可以)
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
guard let m_url = request.mainDocumentURL else{
return request
}
if m_url.absoluteString.contains("XXX"){
var mrequest = request
mrequest.setValue("aaaa", forHTTPHeaderField: "ssdf")
return mrequest
}
return request
}
//判断两个请求是否为同一个请求,如果为同一个请求那么就会使用缓存数据。
//通常都是调用父类的该方法。我们也不许要特殊处理。
override class func requestIsCacheEquivalent(_ aRequest: URLRequest,
to bRequest: URLRequest) -> Bool {
return super.requestIsCacheEquivalent(aRequest, to:bRequest)
}
//开始处理这个请求
override func startLoading() {
let newRequest = (self.request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
//NSURLProtocol接口的setProperty()方法可以给URL请求添加自定义属性。
//(这样把处理过的请求做个标记,下一次就不再处理了,避免无限循环请求)
URLProtocol.setProperty(true, forKey: "WKURLProtocolHandledKey",
in: newRequest)
//使用URLSession从网络获取数据
let defaultConfigObj = URLSessionConfiguration.default
let defaultSession = Foundation.URLSession(configuration: defaultConfigObj,
delegate: self, delegateQueue: nil)
self.dataTask = defaultSession.dataTask(with: self.request)
self.dataTask!.resume()
}
//结束处理这个请求
override func stopLoading() {
self.dataTask?.cancel()
self.dataTask = nil
self.receivedData = nil
self.urlResponse = nil
}
//URLSessionDataDelegate相关的代理方法
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
self.client?.urlProtocol(self, didReceive: response,
cacheStoragePolicy: .notAllowed)
self.urlResponse = response
self.receivedData = NSMutableData()
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
didReceive data: Data) {
self.client?.urlProtocol(self, didLoad: data)
self.receivedData?.append(data)
}
//URLSessionTaskDelegate相关的代理方法
func urlSession(_ session: URLSession, task: URLSessionTask
, didCompleteWithError error: Error?) {
if error != nil {
self.client?.urlProtocol(self, didFailWithError: error!)
} else {
//保存获取到的请求响应数据
self.client?.urlProtocolDidFinishLoading(self)
}
}
}
//使用
//注册URL Loading System协议,让每一个请求都会经过WKURLProtocol处理
URLProtocol.registerClass(WKURLProtocol.self)
但是我们如果使用的是 WKWebView 仅仅执行以上的操作还不行
因为在 UIWebView 时代,按照下面的方式注册一个自定义的 NSURLProtocol 和 CustomURLCache 的子类,
// 注册url拦截
[NSURLProtocol registerClass:[CustomURLProtocol class]];
// 注册缓存加载
[NSURLCache setSharedURLCache:[CustomURLCache new]];
然后就可以在其实现中对 app 内所有的网络请求进行拦截,并加载本地的离线资源(很多 Hybird 框架都是基于此原理)。但在 WKWebView 中的请求却完全不遵从这一规则,网络上文章一般都解释说 WKWebView 的请求是在单独的进程里,所以不走 NSURLProtocol。
这里我们对 wkwebview 做拓展处理
//swift
extension WKWebView{
func supportURLProtocol(){
let selector = Selector(("registerSchemeForCustomProtocol:"))
let vc = WKWebView().value(forKey: "browsingContextController") as AnyObject
let cls = type(of: vc) as AnyObject
_ = cls.perform(selector, with: "http")
_ = cls.perform(selector, with: "https")
}
}
//oc
#import "WKWebView+NSURLProtocol.h"
@implementation WKWebView (NSURLProtocol)
- (void)supportURLProtocol {
Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL selector = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([cls respondsToSelector:selector]) {
// 通过http和https的请求,同理可通过其他的Scheme 但是要满足ULR Loading System
// 以下方法类似:performSelector:withObject:
IMP (*func)(id, SEL, id) = (void *)[cls methodForSelector:selector];
func(cls, selector, @"http"); // 注册http
func(cls, selector, @"https"); // 注册https
}
}
@end
//使用
webview.supportURLProtocol()