【问题标题】:Decoding/Encoding a struct with protocol type properties解码/编码具有协议类型属性的结构
【发布时间】:2019-12-16 09:10:45
【问题描述】:

我正在尝试使用UserDefaults 保存配置数据结构,因此数据结构需要符合Codable 协议。这是我的数据结构:

// 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
}

如果我只是将Codable 添加到Configuration,它将无法正常工作。所以我必须自己实现。

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)
    }
}

对于decode()encode() 的每次调用,我都会收到错误Protocol type 'Connection' cannot conform to 'Decodable/Encodable' because only concrete types can conform to protocols

我可以看到编译器很难识别,应该使用哪个类来解码给定的对象。但我认为对对象进行编码应该很容易,因为Connection 类型的每个对象都实现了encode() 方法。

我知道,问题出在协议上,该协议不能与Decodable/Encodable 一起使用。我将如何更改decode/encode 中的代码,以便我仍然可以将协议与各种实现一起使用?我的猜测是以某种方式告诉decode/encode 使用哪个协议实现。我会很感激这个问题的任何优雅的解决方案!

【问题讨论】:

  • 错误是对的:Codable 需要符合Codable 的具体类。除了泛型之外没有其他解决方法,它实际上在代码执行时提供了一个具体的类型。
  • 谢谢,你能用泛型更详细地解释一下这个想法吗?
  • 见杰里米的回答。这正是我的意思
  • 我假设问题中有一个错字:协议定义为Connection,但在扩展中,它被称为ConnectionConfiguration
  • 谢谢,当我尝试简化问题代码时会发生这种情况......

标签: swift encoding protocols decode codable


【解决方案1】:

Swift 的一个限制是协议不能符合自己。因此fromto 不符合Codable 的要求,看起来很奇怪。

您可以通过使用泛型来绕过它,这基本上意味着您将fromto 声明为符合Codable 的任意类型。方法如下:

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)

所以FT 是符合Connection 的类型。当你在最后一行实例化example 时,编译器推断FSFTPConnectionTFTPConnection

一旦我添加了通用参数,Configuration 就能够在没有扩展名的情况下合成与Codable 的一致性。


为了回答 Sh_kahn 关于具有两个通用参数的观点,我这样做是为了允许 fromto 成为不同类型的连接。如果您始终希望两个连接具有相同的类型,即始终有两个 SFTPConnections 或两个 FTPConnections,您应该像这样声明 Configuration

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

【讨论】:

  • 这与使它们成为属性之间没有区别,您也重复相同的&lt;F: Connection, T: Connection&gt; 它可能是 1
  • @Sh_Khan 制作什么属性?我使用了两个通用参数,以便from 属性与to 属性的类型不同。
  • 谢谢,这样就解决了这个问题。但是如果我想从UserDefaults 加载我所有的Configuration 对象,我会怎么做,这些对象都可能有不同的类型FT?我会打电话给func loadAll&lt;F:Connection, T: Connection&gt;() -&gt; [String: Configuration&lt;F, T&gt;]? { return UserDefaults.standard.dictionaryRepresentation() as? [String: Configuration&lt;F, T&gt;] } 但这行不通,因为每个配置都可能有不同的FT
  • @Codey 我不知道如何解决这个特定问题。当你开始解码时,你已经在对象的init 中,这意味着FT 已经被选中。这可能是使用类和继承的情况。但我建议为它写一个新问题。
  • @JeremyP 我针对这个问题发布了一个新问题:stackoverflow.com/q/59364986/3272409。如果您能看一下,我将不胜感激。不过,谢谢你的回答!
猜你喜欢
  • 2014-10-26
  • 2020-08-16
  • 1970-01-01
  • 2018-05-14
  • 2017-11-20
  • 1970-01-01
  • 2017-11-10
  • 1970-01-01
相关资源
最近更新 更多