KM的博客.

URLProtocol源码

字数统计: 972阅读时长: 5 min
2020/06/30

URLProtocol源码简析

swift标准库URLProtocol地址

一 、 URLProtocol初始化

1.1通过URLRequest初始化protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
open class URLProtocol : NSObject {

private static var _registeredProtocolClasses = [AnyClass]()
private static var _classesLock = NSLock()

public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
self._request = request
self._cachedResponse = cachedResponse
self._client = client ?? _ProtocolClient()
}

private var _request : URLRequest
private var _cachedResponse : CachedURLResponse?
private var _client : URLProtocolClient?

open var client: URLProtocolClient? {
set { self._client = newValue }
get { return self._client }
}
/*@NSCopying*/ open var request: URLRequest {
return _request
}

/*@NSCopying*/ open var cachedResponse: CachedURLResponse? {
return _cachedResponse
}
}

1.2、通过URLSessionTask初始化protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
open class func canInit(with task: URLSessionTask) -> Bool {
guard let request = task.currentRequest else { return false }
return canInit(with: request)
}
public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
let urlRequest = task.originalRequest
self.init(request: urlRequest!, cachedResponse: cachedResponse, client: client)
self.task = task
}
/*@NSCopying*/ open var task: URLSessionTask? {
set { self._task = newValue }
get { return self._task }
}

private var _task : URLSessionTask? = nil

二、 自定义URLProtocol

1、注册protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
open class func registerClass(_ protocolClass: AnyClass) -> Bool {
if protocolClass is URLProtocol.Type {
_classesLock.lock()
guard !_registeredProtocolClasses.contains(where: { $0 === protocolClass }) else {
_classesLock.unlock()
return true
}
_registeredProtocolClasses.append(protocolClass)
_classesLock.unlock()
return true
}
return false
}
open class func unregisterClass(_ protocolClass: AnyClass) {
_classesLock.lock()
if let idx = _registeredProtocolClasses.firstIndex(where: { $0 === protocolClass }) {
_registeredProtocolClasses.remove(at: idx)
}
_classesLock.unlock()
}

2、响应URLRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*======================================================================
Begin responsibilities for protocol implementors
The methods between this set of begin-end markers must be
implemented in order to create a working protocol.
======================================================================*/

open class func canInit(with request: URLRequest) -> Bool {
NSRequiresConcreteImplementation()
}

open class func canonicalRequest(for request: URLRequest) -> URLRequest {
NSRequiresConcreteImplementation()
}

open class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
NSRequiresConcreteImplementation()
}

open func startLoading() {
NSRequiresConcreteImplementation()
}

open func stopLoading() {
NSRequiresConcreteImplementation()
}
/*======================================================================
End responsibilities for protocol implementors
======================================================================*/


3、转发URLRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public protocol URLProtocolClient : NSObjectProtocol {

func urlProtocol(_ protocol: URLProtocol, wasRedirectedTo request: URLRequest, redirectResponse: URLResponse)

func urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse)

func urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)

func urlProtocol(_ protocol: URLProtocol, didLoad data: Data)

func urlProtocolDidFinishLoading(_ protocol: URLProtocol)

func urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)

func urlProtocol(_ protocol: URLProtocol, didReceive challenge: URLAuthenticationChallenge)

func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge)
}

internal class _ProtocolClient : NSObject {
var cachePolicy: URLCache.StoragePolicy = .notAllowed
var cacheableData: [Data]?
var cacheableResponse: URLResponse?
}

三、NFXProtocol实现URLProtocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import Foundation

@objc
open class NFXProtocol: URLProtocol
{
private static let nfxInternalKey = "com.netfox.NFXInternal"

private lazy var session: URLSession = { [unowned self] in
return URLSession(configuration: .default, delegate: self, delegateQueue: nil)
}()

private let model = NFXHTTPModel()
private var response: URLResponse?
private var responseData: NSMutableData?

override open class func canInit(with request: URLRequest) -> Bool
{
return canServeRequest(request)
}

override open class func canInit(with task: URLSessionTask) -> Bool
{
guard let request = task.currentRequest else { return false }
return canServeRequest(request)
}

private class func canServeRequest(_ request: URLRequest) -> Bool
{
guard NFX.sharedInstance().isEnabled() else {
return false
}

guard
URLProtocol.property(forKey: NFXProtocol.nfxInternalKey, in: request) == nil,
let url = request.url,
(url.absoluteString.hasPrefix("http") || url.absoluteString.hasPrefix("https"))
else {
return false
}

let absoluteString = url.absoluteString
guard !NFX.sharedInstance().getIgnoredURLs().contains(where: { absoluteString.hasPrefix($0) }) else {
return false
}

return true
}

override open func startLoading()
{
model.saveRequest(request)

let mutableRequest = (request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
URLProtocol.setProperty(true, forKey: NFXProtocol.nfxInternalKey, in: mutableRequest)
session.dataTask(with: mutableRequest as URLRequest).resume()
}

override open func stopLoading()
{
session.getTasksWithCompletionHandler { dataTasks, _, _ in
dataTasks.forEach { $0.cancel() }
}
}

override open class func canonicalRequest(for request: URLRequest) -> URLRequest
{
return request
}
}

extension NFXProtocol: URLSessionDataDelegate {
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
responseData?.append(data)

client?.urlProtocol(self, didLoad: data)
}

public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
self.response = response
self.responseData = NSMutableData()

client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: NFX.swiftSharedInstance.cacheStoragePolicy)
completionHandler(.allow)
}

public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
defer {
if let error = error {
client?.urlProtocol(self, didFailWithError: error)
} else {
client?.urlProtocolDidFinishLoading(self)
}
}

guard let request = task.originalRequest else {
return
}

model.saveRequestBody(request)
model.logRequest(request)

if error != nil {
model.saveErrorResponse()
} else if let response = response {
let data = (responseData ?? NSMutableData()) as Data
model.saveResponse(response, data: data)
}

NFXHTTPModelManager.sharedInstance.add(model)
NotificationCenter.default.post(name: .NFXReloadData, object: nil)
}

public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {

let updatedRequest: URLRequest
if URLProtocol.property(forKey: NFXProtocol.nfxInternalKey, in: request) != nil {
let mutableRequest = (request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
URLProtocol.removeProperty(forKey: NFXProtocol.nfxInternalKey, in: mutableRequest)

updatedRequest = mutableRequest as URLRequest
} else {
updatedRequest = request
}

client?.urlProtocol(self, wasRedirectedTo: updatedRequest, redirectResponse: response)
completionHandler(updatedRequest)
}

public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let wrappedChallenge = URLAuthenticationChallenge(authenticationChallenge: challenge, sender: NFXAuthenticationChallengeSender(handler: completionHandler))
client?.urlProtocol(self, didReceive: wrappedChallenge)
}

#if !os(OSX)
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
client?.urlProtocolDidFinishLoading(self)
}
#endif

参考

CATALOG
  1. 1. URLProtocol源码简析
    1. 1.1. 一 、 URLProtocol初始化
      1. 1.1.1. 1.1通过URLRequest初始化protocol
      2. 1.1.2. 1.2、通过URLSessionTask初始化protocol
    2. 1.2. 二、 自定义URLProtocol
      1. 1.2.1. 1、注册protocol
      2. 1.2.2. 2、响应URLRequest
      3. 1.2.3. 3、转发URLRequest
    3. 1.3. 三、NFXProtocol实现URLProtocol
    4. 1.4. 参考