-
Notifications
You must be signed in to change notification settings - Fork 687
/
Copy pathNetworkService.swift
executable file
·181 lines (147 loc) · 5.7 KB
/
NetworkService.swift
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//
// NetworkService.swift
// ExampleMVVM
//
// Created by Oleh Kudinov on 01.10.18.
//
import Foundation
public enum NetworkError: Error {
case error(statusCode: Int, data: Data?)
case notConnected
case cancelled
case generic(Error)
case urlGeneration
}
public protocol NetworkCancellable {
func cancel()
}
extension URLSessionTask: NetworkCancellable { }
public protocol NetworkService {
typealias CompletionHandler = (Result<Data?, NetworkError>) -> Void
func request(endpoint: Requestable, completion: @escaping CompletionHandler) -> NetworkCancellable?
}
public protocol NetworkSessionManager {
typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void
func request(_ request: URLRequest,
completion: @escaping CompletionHandler) -> NetworkCancellable
}
public protocol NetworkErrorLogger {
func log(request: URLRequest)
func log(responseData data: Data?, response: URLResponse?)
func log(error: Error)
}
// MARK: - Implementation
public final class DefaultNetworkService {
private let config: NetworkConfigurable
private let sessionManager: NetworkSessionManager
private let logger: NetworkErrorLogger
public init(config: NetworkConfigurable,
sessionManager: NetworkSessionManager = DefaultNetworkSessionManager(),
logger: NetworkErrorLogger = DefaultNetworkErrorLogger()) {
self.sessionManager = sessionManager
self.config = config
self.logger = logger
}
private func request(request: URLRequest, completion: @escaping CompletionHandler) -> NetworkCancellable {
let sessionDataTask = sessionManager.request(request) { data, response, requestError in
if let requestError = requestError {
var error: NetworkError
if let response = response as? HTTPURLResponse {
error = .error(statusCode: response.statusCode, data: data)
} else {
error = self.resolve(error: requestError)
}
self.logger.log(error: error)
completion(.failure(error))
} else {
self.logger.log(responseData: data, response: response)
completion(.success(data))
}
}
logger.log(request: request)
return sessionDataTask
}
private func resolve(error: Error) -> NetworkError {
let code = URLError.Code(rawValue: (error as NSError).code)
switch code {
case .notConnectedToInternet: return .notConnected
case .cancelled: return .cancelled
default: return .generic(error)
}
}
}
extension DefaultNetworkService: NetworkService {
public func request(endpoint: Requestable, completion: @escaping CompletionHandler) -> NetworkCancellable? {
do {
let urlRequest = try endpoint.urlRequest(with: config)
return request(request: urlRequest, completion: completion)
} catch {
completion(.failure(.urlGeneration))
return nil
}
}
}
// MARK: - Default Network Session Manager
// Note: If authorization is needed NetworkSessionManager can be implemented by using,
// for example, Alamofire SessionManager with its RequestAdapter and RequestRetrier.
// And it can be incjected into NetworkService instead of default one.
public class DefaultNetworkSessionManager: NetworkSessionManager {
public init() {}
public func request(_ request: URLRequest,
completion: @escaping CompletionHandler) -> NetworkCancellable {
let task = URLSession.shared.dataTask(with: request, completionHandler: completion)
task.resume()
return task
}
}
// MARK: - Logger
public final class DefaultNetworkErrorLogger: NetworkErrorLogger {
public init() { }
public func log(request: URLRequest) {
print("-------------")
print("request: \(request.url!)")
print("headers: \(request.allHTTPHeaderFields!)")
print("method: \(request.httpMethod!)")
if let httpBody = request.httpBody, let result = ((try? JSONSerialization.jsonObject(with: httpBody, options: []) as? [String: AnyObject]) as [String: AnyObject]??) {
printIfDebug("body: \(String(describing: result))")
} else if let httpBody = request.httpBody, let resultString = String(data: httpBody, encoding: .utf8) {
printIfDebug("body: \(String(describing: resultString))")
}
}
public func log(responseData data: Data?, response: URLResponse?) {
guard let data = data else { return }
if let dataDict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
printIfDebug("responseData: \(String(describing: dataDict))")
}
}
public func log(error: Error) {
printIfDebug("\(error)")
}
}
// MARK: - NetworkError extension
extension NetworkError {
public var isNotFoundError: Bool { return hasStatusCode(404) }
public func hasStatusCode(_ codeError: Int) -> Bool {
switch self {
case let .error(code, _):
return code == codeError
default: return false
}
}
}
extension Dictionary where Key == String {
func prettyPrint() -> String {
var string: String = ""
if let data = try? JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) {
if let nstr = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
string = nstr as String
}
}
return string
}
}
func printIfDebug(_ string: String) {
#if DEBUG
print(string)
#endif
}