Hi @nikakirkitadze and welcome to our community!
As you already know every request may comes with an error so you have to handle the errors too in your generic method.
Lets say that you have the below generic method in order to perform get requests:
func getRequest<T: Codable, U: Codable>(completion: @escaping(_ response: T?, _ error: HTTPClientError<U>?) -> Void) {
URLSession.shared.dataTask(with: URLRequest(url: URL(string: "")!)) { (data, response, error) in
guard let statusCode = (response as? HTTPURLResponse)?.statusCode, let data = data else {
completion(nil, HTTPClientError(type: .invalidResponse, model: nil))
return
}
if statusCode == 400 {
let decodedErrorData = try? JSONDecoder().decode(U.self, from: data)
completion(nil, HTTPClientError(statusCode: statusCode, type: .AUTH_FAILED, model: decodedErrorData))
return
}
// Success
do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
completion(decodedData, nil)
} catch {
print(error)
let decodedErrorData = try? JSONDecoder().decode(U.self, from: data)
completion(nil, HTTPClientError(statusCode: statusCode, type: .parsingError, model: decodedErrorData))
}
}.resume()
}
The ExampleResponse model and the Generic HTTPClientError:
struct ExampleResponse: Codable {
// Add your coding keys here..
}
public final class HTTPClientError<T: Codable>: Error {
public let statusCode: Int?
public let type: Code
public let model: T?
public enum Code: Int {
case none
case invalidResponse
case invalidRequest
case parsingError
case AUTH_FAILED = 401
case FAILED = 500
case SERVICE_UNAVAILABLE = 501
}
public required init(statusCode: Int? = nil, type: Code, model: T? = nil) {
self.statusCode = statusCode
self.type = type
self.model = model
}
}
As you can see we created a generic Error with a Generic Type as Encoding Type.
Now you can call your method like that:
func getExampleResponse(completion: @escaping(_ response: ExampleResponse?, _ error: HTTPClientError<String>?) -> Void) {
getRequest(completion: completion)
}
So regarding your request and the error that you are waiting you can adjust the generic types in order to fit your needs.
The best practise here is:
- Use Swift Codables in order to map your responses
- Always check for errors if any
- Create Generic method in order to avoid duplicate code
You can check my lightweight swift package about networking for additional help : https://github.com/kstefanou52/SKHTTPClient
Update for unknown response type
If you don't know the type of the response you have to try cast the response as shown below:
func getRequest(completion: @escaping(_ response: String?, _ error: [String: String]?) -> Void) {
URLSession.shared.dataTask(with: URLRequest(url: URL(string: "")!)) { (data, response, error) in
guard let statusCode = (response as? HTTPURLResponse)?.statusCode, let data = data else {
completion(nil, ["error": "empty response"])
return
}
if let stringResponse = String(data: data, encoding: .utf8) {
completion(stringResponse, nil)
} else if let dictionaryResponse = try? JSONDecoder().decode([String: String].self, from: data) {
completion(nil, dictionaryResponse)
} else {
completion(nil, ["error": "unknown response"])
}
}.resume()
}