在带有继承的Swift 4中使用Decodable [英] Using Decodable in Swift 4 with Inheritance

查看:86
本文介绍了在带有继承的Swift 4中使用Decodable的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用类继承是否应该破坏类的可解码性。例如,以下代码

  class服务器:可编码{
var id:整数?
}

类开发:服务器{
var name:字符串?
var userId:整数?
}

var json = {\ id\:1,\ name\:\大型建筑开发\}
let jsonDecoder = JSONDecoder()
let item =尝试jsonDecoder.decode(Development.self,from:json.data(using:.utf8)!)as Development

print(item。 id ?? id is nil)
print(item.name ?? name is nil)在这里

输出为:

  1 
名称为nil

现在,如果我将其取反,名称会解码,但id不会。

 类服务器{
var id:Int?
}

类开发:服务器,可编码{
var name:字符串?
var userId:整数?
}

var json = {\ id\:1,\ name\:\大型建筑开发\}
let jsonDecoder = JSONDecoder()
let item =尝试jsonDecoder.decode(Development.self,from:json.data(using:.utf8)!)as Development

print(item。 id ?? id is nil)
print(item.name ?? name is nil)

输出为:

  id为nil 
大型建筑开发

而且两种语言都无法表达Codable。

解决方案

我相信在继承的情况下,您必须自己实现 Coding 。也就是说,您必须指定 CodingKeys 并实现 init(from:) encode(to :)在超类和子类中。根据

 所需的init(来自解码器:解码器)throw {

//获取此子类的编码键$ b $的容器b让容器=尝试解码器。容器(keyedBy:CodingKeys.self)
myVar =尝试容器。解码(MyType.self,forKey:.myVar)
//其他变量= ...

//获取超类的superDecoder并调用super.init(from :)
让superDecoder =试试container.superDecoder()
试试super.init(from:superDecoder)

}

视频似乎停止了编码方面的展示(但它是 container.superE ncoder()用于 encode(to:)端),但是在 encode( to:)实现。在这种简单情况下,我可以确认这项工作有效(请参见下面的操场代码)。



我自己仍在为某些奇怪的行为而苦苦挣扎,而我要从 NSCoding 转换一个更复杂的模型,其中有很多新嵌套的类型(包括 struct enum ),它们表现出这种意外的 nil 行为,并且不应。请注意,可能存在涉及嵌套类型的边缘情况。



编辑:嵌套类型在我的测试环境中似乎可以正常工作;我现在怀疑自引用类(认为是树节点的子代)有问题,而自引用类本身也包含该类的各种子类的实例。一个简单的自引用类的测试可以很好地解码(即没有子类),因此我现在将精力集中在子类案例失败的原因上。



17年6月25日更新:我最终向Apple提交了一个有关此问题的错误。 rdar:// 32911973-不幸的是,包含 Subclass:Superclass 元素的 Superclass 数组的编码/解码周期将导致数组中的所有元素都被解码为 Superclass (子类的 init(from:)从未被调用,

  //:完全实现继承

类FullSuper:可编码{

var id:UUID?

init(){}

私有枚举CodingKeys:字符串,CodingKey {case id}

init(来自解码器:Decoder)抛出{

让容器=尝试解码器。容器(keyedBy:CodingKeys.self)
id =尝试容器.decode(UUID.self,forKey:.id)

}

函数func encode(编码器:Encoder)抛出{

var container = encoder.container(keyedBy:CodingKeys.self)
try container.encode(id ,forKey:.id)

}

}

class FullSub:FullSuper {

var string:String?
私有枚举CodingKeys:字符串,CodingKey {大小写字符串}

覆盖init(){super.init()}

需要init(来自解码器:解码器)抛出{

让容器=尝试解码器。容器(keyedBy:CodingKeys.self)
让superdecoder =尝试容器.superDecoder()
尝试super.init(来自:superdecoder)

字符串=尝试container.decode(String.self,forKey:.string)

}

覆盖func编码(编码器:编码器) throws {

var container = encoder.container(keyedBy:CodingKeys.self)
try container.encode(string,forKey:.string)

let superencoder = container.superEncoder()
试试super.encode(to:superencoder)

}
}

让fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = FullSub

let fullEncoder = PropertyListEncoder()
let fullData =尝试fullEncoder.encode(fullSub)

let fullDecoder = PropertyListDecoder()
let 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆