在继承的 Swift 4 中使用 Decodable [英] Using Decodable in Swift 4 with Inheritance
问题描述
使用类继承是否会破坏类的可解码性.例如下面的代码
class Server : Codable {var id : 整数?}类开发:服务器{变量名称:字符串?var userId : Int?}var json = "{"id" : 1,"name" : "大型建筑开发"}"让 jsonDecoder = JSONDecoder()让 item = 尝试 jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) 作为 Development打印(item.id ?? id 为零")在这里打印(item.name ?? name is nil")
输出为:
1名称为零
现在,如果我反过来,名称会解码,但 id 不会.
class 服务器 {var id : 整数?}类开发:服务器,可编码{变量名称:字符串?var userId : Int?}var json = "{"id" : 1,"name" : "大型建筑开发"}"让 jsonDecoder = JSONDecoder()让 item = 尝试 jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) 作为 Development打印(item.id ?? id 为零")打印(item.name ??名称为零")
输出为:
id 为零大型建筑开发
而且你不能在两个类中都表达 Codable.
我相信在继承的情况下,你必须自己实现Coding
.也就是说,您必须指定 CodingKeys
并在超类和子类中实现 init(from:)
和 encode(to:)
.根据
required init(fromdecoder: Decoder) throws {//为这个子类的编码键获取我们的容器让容器 = 尝试decoder.container(keyedBy: CodingKeys.self)myVar = 尝试 container.decode(MyType.self, forKey: .myVar)//其他变量 = ...//获取超类的 superDecoder 并用它调用 super.init(from:)让 superDecoder = 尝试 container.superDecoder()尝试 super.init(来自:superDecoder)}
视频似乎没有显示编码端(但它是 container.superEncoder()
用于 encode(to:)
端)但它在很多方面都有效在您的 encode(to:)
实现中也是如此.我可以确认这在这个简单的情况下有效(请参阅下面的操场代码).
我自己仍然在努力解决一些奇怪的行为,我正在使用一个更复杂的模型从 NSCoding
转换,它有很多新嵌套的类型(包括 struct
> 和 enum
) 表现出这种意外的 nil
行为并且不应该".请注意,可能存在涉及嵌套类型的边缘情况.
嵌套类型在我的测试场中似乎运行良好;我现在怀疑自引用类(想想树节点的子类)有问题,它本身的集合还包含该类的各种子类的实例.一个简单的自引用类的测试可以很好地解码(即没有子类),所以我现在将精力集中在子类案例失败的原因上.
2017 年 6 月 25 日更新:我最终向 Apple 提交了一个关于此的错误.rdar://32911973 - 不幸的是,包含 Subclass: Superclass
元素的 Superclass
数组的编码/解码循环将导致数组中的所有元素都被解码为 超类
(子类的 init(from:)
永远不会被调用,导致数据丢失或更糟).
//:完全实现的继承类 FullSuper: Codable {变量 ID:UUID?在里面() {}私有枚举 CodingKeys: String, CodingKey { case id }所需的初始化(来自解码器:解码器)抛出 {让容器 = 尝试decoder.container(keyedBy: CodingKeys.self)id = 尝试 container.decode(UUID.self, forKey: .id)}func 编码(编码器:编码器)抛出 {var container = encoder.container(keyedBy: CodingKeys.self)尝试 container.encode(id, forKey: .id)}}类 FullSub:FullSuper {var 字符串:字符串?私有枚举 CodingKeys: String, CodingKey { case string }覆盖 init() { super.init() }所需的初始化(来自解码器:解码器)抛出 {让容器 = 尝试decoder.container(keyedBy: CodingKeys.self)让 superdecoder = 尝试 container.superDecoder()尝试 super.init(来自:superdecoder)string = try container.decode(String.self, forKey: .string)}覆盖 func 编码(编码器:编码器)抛出 {var container = encoder.container(keyedBy: CodingKeys.self)尝试 container.encode(string, forKey: .string)让超级编码器 = container.superEncoder()尝试 super.encode(to: superencoder)}}让 fullSub = FullSub()fullSub.id = UUID()fullSub.string = "FullSub"让 fullEncoder = PropertyListEncoder()让 fullData = 尝试 fullEncoder.encode(fullSub)让 fullDecoder = PropertyListDecoder()让 fullSubDecoded: FullSub = 尝试 fullDecoder.decode(FullSub.self, from: fullData)
父类和子类的属性都在fullSubDecoded
中恢复.
Should the use of class inheritance break the Decodability of class. For example, the following code
class Server : Codable {
var id : Int?
}
class Development : Server {
var name : String?
var userId : Int?
}
var json = "{"id" : 1,"name" : "Large Building Development"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development
print(item.id ?? "id is nil")
print(item.name ?? "name is nil") here
output is:
1
name is nil
Now if I reverse this, name decodes but id does not.
class Server {
var id : Int?
}
class Development : Server, Codable {
var name : String?
var userId : Int?
}
var json = "{"id" : 1,"name" : "Large Building Development"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development
print(item.id ?? "id is nil")
print(item.name ?? "name is nil")
output is:
id is nil
Large Building Development
And you can't express Codable in both classes.
I believe in the case of inheritance you must implement Coding
yourself. That is, you must specify CodingKeys
and implement init(from:)
and encode(to:)
in both superclass and subclass. Per the WWDC video (around 49:28, pictured below), you must call super with the super encoder/decoder.
required init(from decoder: Decoder) throws {
// Get our container for this subclass' coding keys
let container = try decoder.container(keyedBy: CodingKeys.self)
myVar = try container.decode(MyType.self, forKey: .myVar)
// otherVar = ...
// Get superDecoder for superclass and call super.init(from:) with it
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
The video seems to stop short of showing the encoding side (but it's container.superEncoder()
for the encode(to:)
side) but it works in much the same way in your encode(to:)
implementation. I can confirm this works in this simple case (see playground code below).
I'm still struggling with some odd behavior myself with a much more complex model I'm converting from NSCoding
, which has lots of newly-nested types (including struct
and enum
) that's exhibiting this unexpected nil
behavior and "shouldn't be". Just be aware there may be edge cases that involve nested types.
Edit: Nested types seem to work fine in my test playground; I now suspect something wrong with self-referencing classes (think children of tree nodes) with a collection of itself that also contains instances of that class' various subclasses. A test of a simple self-referencing class decodes fine (that is, no subclasses) so I'm now focusing my efforts on why the subclasses case fails.
Update June 25 '17: I ended up filing a bug with Apple about this. rdar://32911973 - Unfortunately an encode/decode cycle of an array of Superclass
that contains Subclass: Superclass
elements will result in all elements in the array being decoded as Superclass
(the subclass' init(from:)
is never called, resulting in data loss or worse).
//: Fully-Implemented Inheritance
class FullSuper: Codable {
var id: UUID?
init() {}
private enum CodingKeys: String, CodingKey { case id }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
}
}
class FullSub: FullSuper {
var string: String?
private enum CodingKeys: String, CodingKey { case string }
override init() { super.init() }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let superdecoder = try container.superDecoder()
try super.init(from: superdecoder)
string = try container.decode(String.self, forKey: .string)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(string, forKey: .string)
let superencoder = container.superEncoder()
try super.encode(to: superencoder)
}
}
let fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = "FullSub"
let fullEncoder = PropertyListEncoder()
let fullData = try fullEncoder.encode(fullSub)
let fullDecoder = PropertyListDecoder()
let fullSubDecoded: FullSub = try fullDecoder.decode(FullSub.self, from: fullData)
Both the super- and subclass properties are restored in fullSubDecoded
.
这篇关于在继承的 Swift 4 中使用 Decodable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!