问题
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 SFTPConnection
s or two FTPConnection
s 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