在我們的日常開發過程中有時需要我們對網絡做攔截操作,諸如對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()