Decoding/Encoding a struct with protocol type properties

谁说胖子不能爱 提交于 2020-02-06 08:08:14

问题


I am trying to save a configuration data structure with UserDefaults, thus the data structure needs to conform to the Codable protocol. This is my data structure:

// Data structure which saves two objects, which conform to the Connection protocol
struct Configuration {
    var from: Connection
    var to: Connection
}

protocol Connection: Codable {
    var path: String { get set }
}


// Two implementations of the Connection protocol
struct SFTPConnection: Connection, Codable {
    var path: String
    var user: String
    var sshKey: String
}

struct FTPConnection: Connection, Codable {
    var path: String
    var user: String
    var password: String
}

If I just add Codable to Configuration, it won't work. So I have to implement it myself.

extension Configuration: Codable {

    enum CodingKeys: String, CodingKey {
        case from, to
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        let from = try container.decode(Connection.self, forKey: .from)
        let to = try container.decode(Connection.self, forKey: .to)

        self.from = from
        self.to = to
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(from, forKey: .from)
        try container.encode(to, forKey: .to)
    }
}

For every call on decode() or encode() I get the error Protocol type 'Connection' cannot conform to 'Decodable/Encodable' because only concrete types can conform to protocols.

I can see that it is difficult for the compiler to identify, which class should be used to decode the given object. But I figured it should be easy to encode an object, since every object of type Connection implements the encode() method.

I know, that the problem lies with the protocol and that the protocol can't be used with Decodable/Encodable. How would I change the code in decode/encode, so that I can still use the protocol with the various implementations? My guess is to somehow tell decode/encode which implementation of the protocol to use. I would appreciate any elegant solutions for this problem!


回答1:


It's a limitation of Swift that a protocol cannot conform to itself. Thus from and to do not conform to Codable as bizarre as that seems.

You can get around it by using generics which basically means you declare from and to as arbitrary types that conform to Codable. Here's how:

struct Configuration<F: Connection, T: Connection>: Codable {
    var from: F
    var to: T
}


let myFrom = SFTPConnection(path: "foo", user: "me", sshKey: "hgfnjsfdjs")
let myTo = FTPConnection(path: "foo", user: "me", password: "hgfnjsfdjs")
let example = Configuration(from: myFrom, to: myTo)

So F and T are types that conform to Connection. When you instantiate example in the last line, the compiler infers F is SFTPConnection and T is FTPConnection.

Once I added the generic parameters, Configuration was able to synthesise the conformance to Codable without the extension.


To answer Sh_kahn's point about having two generic parameters, I did this to allow from and to to be connections of different types. If you always want the two connections to be of the same type i.e. always two SFTPConnections or two FTPConnections you should declare the Configuration like this:

struct Configuration<C: Connection>: Codable {
    var from: C
    var to: C
}


来源:https://stackoverflow.com/questions/59353454/decoding-encoding-a-struct-with-protocol-type-properties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!