swift 使协议在扩展中可编码

8i9zcol2  于 2022-12-21  发布在  Swift
关注(0)|答案(2)|浏览(132)

关于这个主题有很多问题,但我还没有弄清楚为什么我的解决方案还不起作用。
我有一些规定

protocol Foo: Decodable {
    var prop1: String? { get set }
    var prop2: Bool? { get set }
    var prop3: Int? { get set }

    init()
}

enum FooCodingKeys: CodingKey { case prop1, prop2, prop3 }
extension Foo {
    init(from decoder: Decoder) throws {
        self.init()

        let container = try decoder.container(keyedBy: FooCodingKeys.self)
        self.prop1 = try container.decode(String?, forKey: .prop1)
        self.prop2 = try container.decode(Bool?, forKey: .prop2)
        self.prop3 = try container.decode(Int?, forKey: .prop3)
    }

}

从技术上讲,它有一个默认的实现,可以使整个协议完全可解码,编译器一点也不抱怨,现在在一个结构体中,如果我有

enum BarCodingKeys: CodingKey { case foos }
struct Bar: Decodable {
    var foos: [Foo]

    init(from decoder: Decoder) {
        let container = try decoder.container(keyedBy: BarCodingKeys.self)
        self.foos = try container.decode([Foo].self, forKey: .prop1)
    }
}

然后得到错误Protocol 'Foo' as a type cannot conform to 'Decodable'
有没有一种方法可以让协议符合使用扩展的可编码性?如果没有,为什么?

ss2ws0br

ss2ws0br1#

我不确定你的用例是什么,所以我的建议可能不适合,但最简单的方法是告诉编译器你正在解码一个具体的类型,而不是一个协议,但这个具体的类型实现了Foo,所以你这样修改Bar

struct Bar<T: Foo>: Decodable {
    
    var foos: [T]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: BarCodingKeys.self)
        self.foos = try container.decode([T].self, forKey: .foos)
    }
}

所以现在你正在解码[T].self-一个具体的类型,而不是协议。当然,缺点是你必须在解码类本身时提供一个类型,也就是说,你不能说:

JSONDecoder().decode(Bar.self, from: jsonData)

您必须在此处提供类型:

struct Foo1: Foo {
    
    var prop1: String?
    var prop2: Bool?
    var prop3: Int?
}

let a = try JSONDecoder().decode(Bar<Foo1>.self, from: jsonData)

但是,如您所见,Foo1不需要实现init(from decoder: Decoder) throws,正确使用了协议中的init(from decoder: Decoder) throws
补充说明:正如你可能注意到的,我把forKey: .prop1改成了forKey: .foos,因为根据你的代码,你期望一个具有foos属性的对象,它作为一个值包含一个对象数组,这些对象与Foo协议匹配,如下所示:

{ "foos": [
      { "prop1": ...,
        "prop2": ...,
        "prop3": ...
      },
      { "prop1": ...,
        "prop2": ...,
        "prop3": ...
      },
      ...
  ]
}

如果不是这种情况,请提供一个你正在尝试解码的JSON的例子。并且你还需要修复这个函数(使用decodeIfPresent代替可选):

extension Foo {
    init(from decoder: Decoder) throws {
        self.init()

        let container = try decoder.container(keyedBy: FooCodingKeys.self)
        self.prop1 = try container.decodeIfPresent(String.self, forKey: .prop1)
        self.prop2 = try container.decodeIfPresent(Bool.self, forKey: .prop2)
        self.prop3 = try container.decodeIfPresent(Int.self, forKey: .prop3)
    }
}
4uqofj5v

4uqofj5v2#

首先,我认为这里有一个关键的误解:

protocol Foo: Decodable { ... }

在你的主题中,你建议这是“使协议符合Codable”,但这不是它的作用,它说“为了符合Foo,一个类型必须首先符合Decodable”,Foo本身绝对不符合Codable,然后你注意到:
从技术上讲,这现在有一个默认的实现,应该使整个协议完全解码。
这在任何一个方向上都是不正确的。考虑一个简单的例子:

struct ConcreteFoo: Foo {
    var prop1: String?
    var prop2: Bool?
    var prop3: Int?
    var anotherPropNotInFoo: CBPeripheral
 }

你的init(from:)是如何解码ConcreteFoo的?anotherPropNotInFoo的值是多少?
在另一个方向上,考虑三种符合类型:

struct FooA: Foo {
    var prop1: String?
    var prop2: Bool?
    var prop3: Int?
    var anotherProp: Bool = true
}

struct FooA: Foo {
    var prop1: String?
    var prop2: Bool?
    var prop3: Int?
}

struct FooC: Foo {
    var prop1: String?
    var prop2: Bool?
    var prop3: Int?
    var anotherProp: Bool = true
    func doC() { print("I'm a C!") }
}

假设您的JSON为:

[
    {},
    { "anotherProp": false }
]

现在考虑以下代码:

let bar = try JSONDecoder().decode(Bar.self, from: json)
for foo in bar.foos {
    print("\(type(of: foo)")
    if let c = foo as? FooC {
        c.cThing()
    }
}

应该发生什么?它们应该是什么实际类型?记住,可能存在无限数量的其他Foo实现(包括在其他模块中)。这是行不通的。如果你的意思是Foo只有这些属性,没有其他属性,也没有其他方法,那么它就不是一个协议。它只是一个结构。如果你的意思是有无限多的类型实现它,那就无法确定它们是什么。

相关问题