First of all, I'm totally new to Swift and iOS development, but decided to try just for some fun. I'm also taking a Nanodegree from Udacity on the same at the moment. I got the base screen designed, and am now trying to design a easy to use API to define backend calls (not required in class, but I picked it up out of curiosity).
struct ApiDefinition<RequestType: Codable, ResponseType: Codable> {
let url: String
let method: HttpMethod
let headers: [String : String]?
func call(withPayload payload: RequestType, completion: @escaping (ResponseType?, Error?) -> Void) throws {
let url = URL(string: self.url)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 500)
request.httpMethod = method.rawValue
request.httpBody = try JSONEncoder().encode(payload)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
completion(nil, error)
return
}
let decoder = JSONDecoder()
let value = try? decoder.decode(ResponseType.self, from: data)
completion(value, nil)
}
task.resume()
}
}
enum HttpMethod: String {
case get = "GET"
case post = "POST"
}
This is working as expected, and I define the endpoints as follows.
let udacitySessionApi = ApiDefinition<SessionRequest, SessionResponse>(
url: baseUrl + "/v1/session",
method: .post,
headers: [
"Content-Type": "application/json",
"Accept": "application/json"
]
)
Which in turn lets me to call the API whenever I want to with the following code.
do {
try udacitySessionApi.call(withPayload: request) { response, error in
guard let response = response else {
print(error!.localizedDescription)
return
}
print(response)
}
} catch {
print(error.localizedDescription)
}
So far, so good. Now what I wanted to do is allow the Codable to wrap itself for conciseness. However, the problem now is that some request types often needs to be nested in the encoded output, and I didn't want to write extra structs.
So I thought if the RequestType is a Codable and also conforms to a Wrappable protocol (I wrote), I can automate that.
So I wrote the protocol Wrappable as follows:
protocol Wrappable: Codable {
func wrap() -> Codable
}
And then in my request type I made it conform to that protocol.
struct SessionRequest: Codable, Wrappable {
// ...
func wrap() -> Codable {
return ["wrapped-key": self]
}
}
Now to access this thing, I wrote a function
private func getEncodable<T: Codable>(_ payload: T) -> Codable {
if let payload = payload as? Wrappable {
return payload.wrap()
}
return payload
}
But problem now came with the JSONEncoder that the signature of encode is
open func encode<T>(_ value: T) throws -> Data where T : Encodable
and the error message that I received is this:
Value of protocol type 'Codable' (aka 'Decodable & Encodable') cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
happening on the line
request.httpBody = try JSONEncoder().encode(getEncodable(payload))
// ^^
As far as I understand, if a struct confirms to Codable
protocol, then it automatically confirms to the Encodable
protocol too isn't it?
So, I thought to explicitly change Codable return type in my wrap function, the Wrappable protocol and also the getEncodable function to Encodable, and my error message changed to this:
Value of protocol type 'Encodable' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
Great, now I'm even more confused. What does value of protocol type mean?