0

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?

Sri Harsha Chilakapati
  • 11,744
  • 6
  • 50
  • 91
  • A protocol can inherit from another protocol but it can’t conform (implement) to it. A protocol says that for instance it requires a variable x of type Int or a function y that returns a string but a protocol can’t implement the variable or the function, that is something only enum, struct and class types can do – Joakim Danielson Mar 26 '21 at 11:52
  • Not an answer to your question, but your completion handler should use a `Result` type. Rather than accepting an optional response and a optional error it should accept `Result` – Paulw11 Mar 26 '21 at 11:54
  • 2
    See [this excellent answer](https://stackoverflow.com/a/43408193/5133585) for why protocols don't conform to protocols. – Sweeper Mar 26 '21 at 11:56
  • @JoakimDanielson Does that mean I'm somehow returning a type instead of a value of that type? – Sri Harsha Chilakapati Mar 26 '21 at 11:57
  • @Paulw11 I am not aware of the existence of the `Result` type in Swift. Will start using it, thanks for the nice tip! – Sri Harsha Chilakapati Mar 26 '21 at 11:58
  • @Sweeper I took some time to read that answer, but some things are slightly getting over my mind. From what I understand, it is that a protocol never conforms to itself. But if that is the case, then we shouldn't be able to downcast a reference too isn't it? – Sri Harsha Chilakapati Mar 26 '21 at 15:33
  • So in case of protocols, even if an object conforms to Codable, it is not a Codable right? How to get over this restriction? Will opaque types work? Let me try it out.. I guess when I specify the return type to `some Codable`, then I should be getting something that conforms to Codable. – Sri Harsha Chilakapati Mar 26 '21 at 15:37

0 Answers0