forked from mike123789-dev/APIKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSession.swift
More file actions
127 lines (107 loc) · 5.78 KB
/
Copy pathSession.swift
File metadata and controls
127 lines (107 loc) · 5.78 KB
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
import Foundation
private var taskRequestKey = 0
/// `Session` manages tasks for HTTP/HTTPS requests.
open class Session {
/// The adapter that connects `Session` instance and lower level backend.
public let adapter: SessionAdapter
/// The default callback queue for `send(_:handler:)`.
public let callbackQueue: CallbackQueue
/// Returns `Session` instance that is initialized with `adapter`.
/// - parameter adapter: The adapter that connects lower level backend with Session interface.
/// - parameter callbackQueue: The default callback queue for `send(_:handler:)`.
public init(adapter: SessionAdapter, callbackQueue: CallbackQueue = .main) {
self.adapter = adapter
self.callbackQueue = callbackQueue
}
// Shared session for class methods
private static let privateShared: Session = {
let configuration = URLSessionConfiguration.default
let adapter = URLSessionAdapter(configuration: configuration)
return Session(adapter: adapter)
}()
/// The shared `Session` instance for class methods, `Session.send(_:handler:)` and `Session.cancelRequests(with:passingTest:)`.
open class var shared: Session {
return privateShared
}
/// Calls `send(_:callbackQueue:handler:)` of `Session.shared`.
/// - parameter request: The request to be sent.
/// - parameter callbackQueue: The queue where the handler runs. If this parameters is `nil`, default `callbackQueue` of `Session` will be used.
/// - parameter handler: The closure that receives result of the request.
/// - returns: The new session task.
@discardableResult
open class func send<Request: APIKit.Request>(_ request: Request, callbackQueue: CallbackQueue? = nil, handler: @escaping (Result<Request.Response, SessionTaskError>) -> Void = { _ in }) -> SessionTask? {
return shared.send(request, callbackQueue: callbackQueue, handler: handler)
}
/// Calls `cancelRequests(with:passingTest:)` of `Session.shared`.
open class func cancelRequests<Request: APIKit.Request>(with requestType: Request.Type, passingTest test: @escaping (Request) -> Bool = { _ in true }) {
shared.cancelRequests(with: requestType, passingTest: test)
}
/// Sends a request and receives the result as the argument of `handler` closure. This method takes
/// a type parameter `Request` that conforms to `Request` protocol. The result of passed request is
/// expressed as `Result<Request.Response, SessionTaskError>`. Since the response type
/// `Request.Response` is inferred from `Request` type parameter, the it changes depending on the request type.
/// - parameter request: The request to be sent.
/// - parameter callbackQueue: The queue where the handler runs. If this parameters is `nil`, default `callbackQueue` of `Session` will be used.
/// - parameter handler: The closure that receives result of the request.
/// - returns: The new session task.
@discardableResult
open func send<Request: APIKit.Request>(_ request: Request, callbackQueue: CallbackQueue? = nil, handler: @escaping (Result<Request.Response, SessionTaskError>) -> Void = { _ in }) -> SessionTask? {
let task = createSessionTask(request, callbackQueue: callbackQueue, handler: handler)
task?.resume()
return task
}
/// Cancels requests that passes the test.
/// - parameter requestType: The request type to cancel.
/// - parameter test: The test closure that determines if a request should be cancelled or not.
open func cancelRequests<Request: APIKit.Request>(with requestType: Request.Type, passingTest test: @escaping (Request) -> Bool = { _ in true }) {
adapter.getTasks { [weak self] tasks in
tasks
.filter { task in
if let request = self?.requestForTask(task) as Request? {
return test(request)
} else {
return false
}
}
.forEach { $0.cancel() }
}
}
internal func createSessionTask<Request: APIKit.Request>(_ request: Request, callbackQueue: CallbackQueue?, handler: @escaping (Result<Request.Response, SessionTaskError>) -> Void) -> SessionTask? {
let callbackQueue = callbackQueue ?? self.callbackQueue
let urlRequest: URLRequest
do {
urlRequest = try request.buildURLRequest()
} catch {
callbackQueue.execute {
handler(.failure(.requestError(error)))
}
return nil
}
let task = adapter.createTask(with: urlRequest) { data, urlResponse, error in
let result: Result<Request.Response, SessionTaskError>
switch (data, urlResponse, error) {
case (_, _, let error?):
result = .failure(.connectionError(error))
case (let data?, let urlResponse as HTTPURLResponse, _):
do {
result = .success(try request.parse(data: data as Data, urlResponse: urlResponse))
} catch {
result = .failure(.responseError(error))
}
default:
result = .failure(.responseError(ResponseError.nonHTTPURLResponse(urlResponse)))
}
callbackQueue.execute {
handler(result)
}
}
setRequest(request, forTask: task)
return task
}
private func setRequest<Request: APIKit.Request>(_ request: Request, forTask task: SessionTask) {
objc_setAssociatedObject(task, &taskRequestKey, request, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
private func requestForTask<Request: APIKit.Request>(_ task: SessionTask) -> Request? {
return objc_getAssociatedObject(task, &taskRequestKey) as? Request
}
}