协议类型“Encodable”的值不能符合“Encodable”;只有结构/枚举/类类型可以符合协议

Posted

技术标签:

【中文标题】协议类型“Encodable”的值不能符合“Encodable”;只有结构/枚举/类类型可以符合协议【英文标题】:Value of protocol type 'Encodable' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols 【发布时间】:2020-06-07 22:34:17 【问题描述】:

我有以下 Swift 代码

func doStuff<T: Encodable>(payload: [String: T]) 
    let jsonData = try! JSONEncoder().encode(payload)
    // Write to file


var things: [String: Encodable] = [
    "Hello": "World!",
    "answer": 42,
]

doStuff(payload: things)

导致错误

Value of protocol type 'Encodable' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols

如何解决?我想我需要更改things 的类型,但我不知道该怎么做。

附加信息:

如果我将 doStuff 更改为不通用,我只会在该函数中遇到同样的问题

func doStuff(payload: [String: Encodable]) 
    let jsonData = try! JSONEncoder().encode(payload) // Problem is now here
    // Write to file

【问题讨论】:

你的目标是什么?你想达到什么目的?您打算将字典值作为协议而不是类型做什么? @matt 我最初的问题可能不够清楚。我需要将参数序列化为doStuff。如果我将签名更改为您的建议,我将遇到与我描述的完全相同的问题,就在我调用编码函数时。 【参考方案1】:

Encodable 不能用作带注释的类型。它只能用作通用约束。而JSONEncoder 只能编码具体类型。

功能

func doStuff<T: Encodable>(payload: [String: T]) 

是正确的,但您不能使用[String: Encodable] 调用该函数,因为协议不能符合自身。这正是错误消息所说的内容。


主要问题是things的真实类型是[String:Any],而Any无法编码。

您必须使用JSONSerialization 序列化things 或创建一个辅助结构。

【讨论】:

谢谢。我害怕这个。拥有一个辅助结构极大地限制了我,因为我只想编码和转储任意数据(符合 Encodable 协议)。 protocol 的全部意义不在于它声明了一个函数以便可以随时调用它吗?如果它有像​​open func encode&lt;T&gt;(_ value: T) throws -&gt; Data where T : Encodable 这样的函数,我希望它是可调用的。 Swift 还需要什么?这完全扼杀了使用接口的整个想法。接下来是什么,description 也需要一个特定的实例? @vadian 我仍然无法理解。我正在存储Encodable 类型的东西,它有一个函数encode(它指定了这个合约)。这意味着所有实例都有一个函数encode。这意味着我应该可以调用它。 @vadian 这将是protocol 的全部意义,因此我们不必在运行时关心单个具体类型,并且仍然调用它声明的函数。如果某个东西是Encodable,它已经实现了encode(它必须)。这意味着我不能写if let encodable = anyObject as? Encodable try JSONEncoder().encode(payload) ,这对我来说很荒谬。 感谢您的解释,但来自其他语言的这种语言功能(限制)对我来说听起来绝对荒谬。 [String: Encodable] because a protocol cannot conform to itself. 通过指定 [String: Encodable](至少在 c# 或 typescript 等语言中)我们不是要求接口(或本例中的协议)符合自身,我们不能创建接口的实例,所以值只能是符合协议的 struct/class/enum 的实例。【参考方案2】:

您可以将where 关键字与Value 类型结合使用,如下所示:

func doStuff<Value>(payload: Value) where Value : Encodable 
    ...

【讨论】:

这成功了!!!不需要辅助结构 XD【参考方案3】:

您正在尝试使T 符合Encodable,如果T == Encodable 则这是不可能的。协议不符合自身。

你可以试试:

func doStuff<T: Hashable>(with items: [T: Encodable]) 
    ...

【讨论】:

字典键不是问题。这是价值 (Encodable)。当我将它传递给另一个函数时,我遇到了同样的问题。例如一个json编码器。我已经更新了问题以反映这一点。【参考方案4】:

通过协议扩展,您可以尝试做的事情:

protocol JsonEncoding where Self: Encodable  

extension JsonEncoding 
    func encode(using encoder: JSONEncoder) throws -> Data 
        try encoder.encode(self)
    


extension Dictionary where Value == JsonEncoding 
    func encode(using encoder: JSONEncoder) throws -> [Key: String] 
        try compactMapValues 
            try String(data: $0.encode(using: encoder), encoding: .utf8)
        
    

Dictionary 中可能存在的每种类型都需要符合我们的JsonEncoding 协议。您使用了StringInt,所以这里有一些扩展来增加这两种类型的一致性:

extension String: JsonEncoding  
extension Int: JsonEncoding  

这是你的代码做你想做的事:

func doStuff(payload: [String: JsonEncoding]) 
    let encoder = JSONEncoder()
    do 
        let encodedValues = try payload.encode(using: encoder)
        let jsonData = try encoder.encode(encodedValues)
        
        // Write to file
     catch 
        print(error)
    


var things: [String: JsonEncoding] = [
    "Hello": "World!",
    "answer": 42
]

doStuff(payload: things)

您没有询问解码,所以我没有在这里解决。您要么必须知道什么类型与什么键相关联,要么必须创建一个尝试解码值的顺序(应该将1 转换为IntDouble。 ..)。

我在没有质疑你的动机的情况下回答了你的问题,但对于你正在尝试做的事情,可能有一个更好、更“迅速”的解决方案......

【讨论】:

不错的解决方案,@DudeOnRock

以上是关于协议类型“Encodable”的值不能符合“Encodable”;只有结构/枚举/类类型可以符合协议的主要内容,如果未能解决你的问题,请参考以下文章

类型 '' 不符合协议 'Decodable'/'Encodable'

不符合协议 Decodabel 和 Encodable

如何使用Alamofire Router来组织API调用?(swift Alamofire5)

使用 Swift 的 Encodable 将可选属性编码为 null 而无需自定义编码

协议类型不能符合协议,因为只有具体类型才能符合协议

请帮忙:类型'()'不能符合'View';只有结构/枚举/类类型可以符合协议