Skip to content

Commit 9860535

Browse files
Merge pull request #18 from AppcentMobile/feature/stream-support
Feature/stream support
2 parents ed9909b + 3351cda commit 9860535

File tree

7 files changed

+294
-127
lines changed

7 files changed

+294
-127
lines changed

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import PackageDescription
55

66
let package = Package(
77
name: "ACMNetworking",
8+
platforms: [
9+
.iOS(.v13), // Set the minimum iOS version here
10+
],
811
products: [
912
// Products define the executables and libraries a package produces, and make them visible to other packages.
1013
.library(

Sources/ACMNetworking/ACMNetworking.swift

Lines changed: 12 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -29,137 +29,27 @@ public class ACMNetworking: NSObject {
2929
onSuccess: ACMGenericCallbacks.ResponseCallback<T>,
3030
onError: ACMGenericCallbacks.ErrorCallback)
3131
{
32-
guard let urlRequest = baseRequest(to: endpoint) else {
33-
ACMBaseLogger.error(ACMNetworkConstants.urlRequestErrorMessage)
34-
return
35-
}
36-
37-
task = endpoint.session(delegate: self).dataTask(with: urlRequest) { data, response, error in
38-
guard error == nil else {
39-
self.cancel()
40-
let message = ACMStringUtils.shared.merge(list: [
41-
ACMNetworkConstants.errorMessage,
42-
error?.localizedDescription ?? "",
43-
])
44-
ACMBaseLogger.error(message)
45-
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: error?.localizedDescription, endpoint: endpoint))
46-
return
47-
}
48-
49-
guard response != nil else {
50-
self.cancel()
51-
let message = ACMStringUtils.shared.merge(list: [
52-
ACMNetworkConstants.errorMessage,
53-
ACMNetworkConstants.responseNullMessage,
54-
])
55-
ACMBaseLogger.error(message)
56-
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.responseNullMessage, endpoint: endpoint))
57-
return
58-
}
59-
60-
guard let data = data else {
61-
self.cancel()
62-
let message = ACMStringUtils.shared.merge(list: [
63-
ACMNetworkConstants.errorMessage,
64-
ACMNetworkConstants.dataNullMessage,
65-
])
66-
ACMBaseLogger.error(message)
67-
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.dataNullMessage, endpoint: endpoint))
68-
return
69-
}
32+
guard let urlRequest = generateURLRequest(endpoint: endpoint) else { return }
7033

71-
if error?.isConnectivityError ?? false {
72-
self.cancel()
73-
let message = ACMStringUtils.shared.merge(list: [
74-
ACMNetworkConstants.errorMessage,
75-
ACMNetworkConstants.dataNullMessage,
76-
])
77-
ACMBaseLogger.error(message)
78-
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.dataNullMessage, endpoint: endpoint))
79-
return
80-
}
34+
task = endpoint.session(delegate: self).dataTask(with: urlRequest) { [weak self] data, response, error in
35+
guard let self else { return }
8136

82-
guard let httpResponse = response as? HTTPURLResponse else {
83-
self.cancel()
84-
let message = ACMStringUtils.shared.merge(list: [
85-
ACMNetworkConstants.errorMessage,
86-
ACMNetworkConstants.httpStatusError,
87-
])
88-
ACMBaseLogger.error(message)
89-
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.httpStatusError, endpoint: endpoint))
90-
return
91-
}
37+
self.handleNilErrorResponse(with: endpoint, error: error, onError: onError)
38+
self.handleNilResponse(with: endpoint, response: response, onError: onError)
39+
self.handleConnectivityError(with: endpoint, error: error, onError: onError)
9240

93-
guard 200 ..< 300 ~= httpResponse.statusCode else {
94-
let message = ACMStringUtils.shared.merge(list: [
95-
ACMNetworkConstants.errorMessage,
96-
ACMNetworkConstants.httpStatusError,
97-
"-\(httpResponse.statusCode)",
98-
ACMNetworkConstants.responseInfoMessage,
99-
String(data: data, encoding: .utf8) ?? ""
100-
])
101-
ACMBaseLogger.error(message)
102-
103-
// MARK: Retry mechanism
104-
105-
guard let maxRetryCount = endpoint.retryCount else {
106-
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.httpStatusError, endpoint: endpoint))
107-
self.cancel()
108-
return
109-
}
110-
111-
if let currentRetryCount = currentRetryCount, currentRetryCount < maxRetryCount {
112-
let nextRetryCount = currentRetryCount + 1
113-
ACMBaseLogger.info(ACMStringUtils.shared.merge(list: [
114-
String(format: ACMNetworkConstants.httpRetryCount, nextRetryCount, maxRetryCount),
115-
]))
116-
self.request(to: endpoint, currentRetryCount: nextRetryCount, onSuccess: onSuccess, onError: onError)
117-
} else {
118-
self.cancel()
119-
}
41+
guard let data = self.handleData(with: endpoint, data: data, onError: onError) else { return }
42+
guard let httpResponse = self.handleHttpResponse(with: endpoint, response: response, onError: onError) else { return }
12043

44+
// Check if response is in valid http range
45+
guard self.validateResponse(with: httpResponse) else {
46+
self.executeRetry(with: endpoint, httpResponse: httpResponse, data: data, currentRetryCount: currentRetryCount, onSuccess: onSuccess, onError: onError)
12147
return
12248
}
12349

12450
self.cancel()
12551

126-
do {
127-
let info = ACMStringUtils.shared.merge(list: [
128-
ACMNetworkConstants.responseInfoMessage,
129-
String(data: data, encoding: .utf8) ?? "",
130-
])
131-
ACMBaseLogger.info(info)
132-
133-
let responseObject = try JSONDecoder().decode(T.self, from: data)
134-
onSuccess?(responseObject)
135-
} catch let DecodingError.dataCorrupted(context) {
136-
let message = ACMStringUtils.shared.merge(list: [
137-
context.debugDescription,
138-
])
139-
ACMBaseLogger.error(message)
140-
} catch let DecodingError.keyNotFound(key, context) {
141-
let message = ACMStringUtils.shared.merge(list: [
142-
"Key \(key) not found: \(context.debugDescription)",
143-
"codingPath: \(context.codingPath)",
144-
])
145-
ACMBaseLogger.error(message)
146-
} catch let DecodingError.valueNotFound(value, context) {
147-
let message = ACMStringUtils.shared.merge(list: [
148-
"Value \(value) not found: \(context.debugDescription)",
149-
"codingPath: \(context.codingPath)",
150-
])
151-
ACMBaseLogger.error(message)
152-
} catch let DecodingError.typeMismatch(type, context) {
153-
let message = ACMStringUtils.shared.merge(list: [
154-
"Type \(type) mismatch: \(context.debugDescription)",
155-
"codingPath: \(context.codingPath)",
156-
])
157-
ACMBaseLogger.error(message)
158-
} catch let e {
159-
let errorMessage = String(format: ACMNetworkConstants.dataParseErrorMessage, e.localizedDescription)
160-
ACMBaseLogger.warning(errorMessage)
161-
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: errorMessage, endpoint: endpoint))
162-
}
52+
self.handleResult(with: endpoint, data: data, onSuccess: onSuccess, onError: onError)
16353
}
16454
task?.resume()
16555
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//
2+
// ACMNetworking+Handle+Extensions.swift
3+
//
4+
//
5+
// Created by Burak on 16.12.2023.
6+
//
7+
8+
import Foundation
9+
10+
extension ACMNetworking {
11+
func generateURLRequest(endpoint: ACMBaseEndpoint) -> URLRequest? {
12+
guard let urlRequest = baseRequest(to: endpoint) else {
13+
ACMBaseLogger.error(ACMNetworkConstants.urlRequestErrorMessage)
14+
return nil
15+
}
16+
return urlRequest
17+
}
18+
}
19+
20+
extension ACMNetworking {
21+
/// Handle if error occures
22+
func handleNilErrorResponse(with endpoint: ACMBaseEndpoint, error: Error?, onError: ACMGenericCallbacks.ErrorCallback) {
23+
guard error == nil else {
24+
cancel()
25+
let message = ACMStringUtils.shared.merge(list: [
26+
ACMNetworkConstants.errorMessage,
27+
error?.localizedDescription ?? "",
28+
])
29+
ACMBaseLogger.error(message)
30+
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: error?.localizedDescription, endpoint: endpoint))
31+
return
32+
}
33+
}
34+
}
35+
36+
extension ACMNetworking {
37+
/// Handle if response is nil
38+
func handleNilResponse(with endpoint: ACMBaseEndpoint, response: URLResponse?, onError: ACMGenericCallbacks.ErrorCallback) {
39+
guard response != nil else {
40+
cancel()
41+
let message = ACMStringUtils.shared.merge(list: [
42+
ACMNetworkConstants.errorMessage,
43+
ACMNetworkConstants.responseNullMessage,
44+
])
45+
ACMBaseLogger.error(message)
46+
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.responseNullMessage, endpoint: endpoint))
47+
return
48+
}
49+
}
50+
}
51+
52+
extension ACMNetworking {
53+
/// Handle some connectivity error occures
54+
func handleConnectivityError(with endpoint: ACMBaseEndpoint, error: Error?, onError: ACMGenericCallbacks.ErrorCallback) {
55+
if error?.isConnectivityError ?? false {
56+
cancel()
57+
let message = ACMStringUtils.shared.merge(list: [
58+
ACMNetworkConstants.errorMessage,
59+
ACMNetworkConstants.dataNullMessage,
60+
])
61+
ACMBaseLogger.error(message)
62+
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.dataNullMessage, endpoint: endpoint))
63+
return
64+
}
65+
}
66+
}
67+
68+
extension ACMNetworking {
69+
/// Handle response data
70+
func handleData(with endpoint: ACMBaseEndpoint, data: Data?, onError: ACMGenericCallbacks.ErrorCallback) -> Data? {
71+
guard let data = data else {
72+
cancel()
73+
let message = ACMStringUtils.shared.merge(list: [
74+
ACMNetworkConstants.errorMessage,
75+
ACMNetworkConstants.dataNullMessage,
76+
])
77+
ACMBaseLogger.error(message)
78+
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.dataNullMessage, endpoint: endpoint))
79+
return nil
80+
}
81+
return data
82+
}
83+
}
84+
85+
extension ACMNetworking {
86+
/// Handle http response
87+
func handleHttpResponse(with endpoint: ACMBaseEndpoint, response: URLResponse?, onError: ACMGenericCallbacks.ErrorCallback) -> HTTPURLResponse? {
88+
guard let httpResponse = response as? HTTPURLResponse else {
89+
cancel()
90+
let message = ACMStringUtils.shared.merge(list: [
91+
ACMNetworkConstants.errorMessage,
92+
ACMNetworkConstants.httpStatusError,
93+
])
94+
ACMBaseLogger.error(message)
95+
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.httpStatusError, endpoint: endpoint))
96+
return nil
97+
}
98+
return httpResponse
99+
}
100+
}
101+
102+
extension ACMNetworking {
103+
/// Validates response with http success statuses
104+
func validateResponse(with httpResponse: HTTPURLResponse) -> Bool {
105+
return 200 ..< 300 ~= httpResponse.statusCode
106+
}
107+
}
108+
109+
extension ACMNetworking {
110+
/// Execute retry mechanism
111+
func executeRetry<T: Decodable>(with endpoint: ACMBaseEndpoint, httpResponse: HTTPURLResponse, data: Data, currentRetryCount: Int?, onSuccess: ACMGenericCallbacks.ResponseCallback<T>, onError: ACMGenericCallbacks.ErrorCallback) {
112+
let message = ACMStringUtils.shared.merge(list: [
113+
ACMNetworkConstants.errorMessage,
114+
ACMNetworkConstants.httpStatusError,
115+
"-\(httpResponse.statusCode)",
116+
ACMNetworkConstants.responseInfoMessage,
117+
String(data: data, encoding: .utf8) ?? "",
118+
])
119+
ACMBaseLogger.error(message)
120+
121+
// MARK: Retry mechanism
122+
123+
guard let maxRetryCount = endpoint.retryCount else {
124+
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: ACMNetworkConstants.httpStatusError, endpoint: endpoint))
125+
cancel()
126+
return
127+
}
128+
129+
if let currentRetryCount = currentRetryCount, currentRetryCount < maxRetryCount {
130+
let nextRetryCount = currentRetryCount + 1
131+
ACMBaseLogger.info(ACMStringUtils.shared.merge(list: [
132+
String(format: ACMNetworkConstants.httpRetryCount, nextRetryCount, maxRetryCount),
133+
]))
134+
request(to: endpoint, currentRetryCount: nextRetryCount, onSuccess: onSuccess, onError: onError)
135+
} else {
136+
cancel()
137+
}
138+
}
139+
}
140+
141+
extension ACMNetworking {
142+
/// Handle server response
143+
func handleResult<T: Decodable>(with endpoint: ACMBaseEndpoint, data: Data, onSuccess: ACMGenericCallbacks.ResponseCallback<T>, onError: ACMGenericCallbacks.ErrorCallback) {
144+
do {
145+
let dataString = String(data: data, encoding: .utf8) ?? ""
146+
let info = ACMStringUtils.shared.merge(list: [
147+
ACMNetworkConstants.responseInfoMessage,
148+
dataString,
149+
])
150+
ACMBaseLogger.info(info)
151+
152+
if endpoint.isStream == true {
153+
let components = dataString
154+
.components(separatedBy: "\n").filter { !$0.replacingOccurrences(of: " ", with: "").isEmpty }
155+
.map { $0.replacingOccurrences(of: "data:", with: "")
156+
.replacingOccurrences(of: " ", with: "")
157+
}
158+
.filter { !$0.contains("DONE") }
159+
.joined(separator: ",")
160+
161+
let componentMerged = String(format: "[%@]", components)
162+
163+
if let data = componentMerged.toData {
164+
let responseObject = try JSONDecoder().decode(T.self, from: data)
165+
onSuccess?(responseObject)
166+
}
167+
} else {
168+
let responseObject = try JSONDecoder().decode(T.self, from: data)
169+
onSuccess?(responseObject)
170+
}
171+
} catch let DecodingError.dataCorrupted(context) {
172+
let message = ACMStringUtils.shared.merge(list: [
173+
context.debugDescription,
174+
])
175+
ACMBaseLogger.error(message)
176+
} catch let DecodingError.keyNotFound(key, context) {
177+
let message = ACMStringUtils.shared.merge(list: [
178+
"Key \(key) not found: \(context.debugDescription)",
179+
"codingPath: \(context.codingPath)",
180+
])
181+
ACMBaseLogger.error(message)
182+
} catch let DecodingError.valueNotFound(value, context) {
183+
let message = ACMStringUtils.shared.merge(list: [
184+
"Value \(value) not found: \(context.debugDescription)",
185+
"codingPath: \(context.codingPath)",
186+
])
187+
ACMBaseLogger.error(message)
188+
} catch let DecodingError.typeMismatch(type, context) {
189+
let message = ACMStringUtils.shared.merge(list: [
190+
"Type \(type) mismatch: \(context.debugDescription)",
191+
"codingPath: \(context.codingPath)",
192+
])
193+
ACMBaseLogger.error(message)
194+
} catch let e {
195+
let errorMessage = String(format: ACMNetworkConstants.dataParseErrorMessage, e.localizedDescription)
196+
ACMBaseLogger.warning(errorMessage)
197+
onError?(ACMBaseNetworkError(message: ACMNetworkConstants.errorMessage, log: errorMessage, endpoint: endpoint))
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)