使协议在扩展上符合可编码性



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

有一些协议

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)
}
}
从技术上讲,这现在有一个默认的实现,它应该使整个协议完全可解码。编译器根本不会抱怨这个。那么现在在struct中,如果我输入
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'.

是否有一种方法让我的协议符合可编码使用扩展?如果不是,为什么?

我不确定您的用例是什么,所以我的建议可能不适合,但最简单的方法是告诉编译器您正在解码具体类型,而不是协议。但是这种具体类型实现了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,因此正确使用了来自协议的一个。

附加说明:正如您可能注意到的,我将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)
}
}

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

protocol Foo: Decodable { ... }

在您的主题中,您建议这是"符合可编码的协议",";但这不是它的作用。这就是说"为了符合Foo,一个类型必须首先符合可解码"。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只有这些属性,没有其他属性,也没有其他方法,那么它就不是一个协议。它只是一个结构体。如果你的意思是有无限多的类型来实现它,那么就没有办法确定它们是什么。

最新更新