我们先定义一组用于演示的支持Codable
的派生类:
public class Shape: Codable {
public required init() {}
public required init(from decoder: Decoder) throws {}
public func getName() {
fatalError("Shape is a general base.")
}
}
public class Triangle: Shape {
public required init() {
super.init()
}
public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
public override func getName() {
print("Triangle")
}
}
public class Rectangle: Shape {
public required init() {
super.init()
}
public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
public override func getName() {
print("Rectangle")
}
}
public class Circle: Shape {
public required init() {
super.init()
}
public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
public override func getName() {
print("Circle")
}
}
接下来,定义一个包含这些类对象的数组:
let shapes = [Triangle(), Rectangle(), Circle()]
现在,如果编码再解码shapes
会发生什么呢?
let collData = try JSONEncoder().encode(shapes)
let collString = String(decoding: collData, as: UTF8.self)
let decoded = try JSONDecoder().decode([Shape].self, from: collData)
decoded.forEach {
$0.getName()
}
执行一下就会在控制台看到Shape is a general base.
的运行时错误了。也就是说,在编码和解码的过程里,shapes
中对象的类型信息会丢掉。decoded
只是一个普通的[Shape]
。因此,调用getName
的时候,就发生错误了。
于是,接下来的问题就变成了,该如何在编码和解码的过程中,保留住类型信息呢?一个可行的办法,就是通过enum
的关联值,把每个类型的对象单独保存起来。为此,我们可以给Shape
添加这样一个扩展:
extension Shape {
enum CodableShape: Codable {
case triganle(Triangle)
case rectangle(Rectangle)
case circle(Circle)
private enum CodingKeys: String, CodingKey {
case triangle
case rectangle
case circle
}
}
}
CodableShape
的每一个case
对应类继承体系中的一个类型。这样,在编码CodableShape
的时候,我们就可以通过CodingKeys
记录对象的类型了:
enum CodableShape: Codable {
/// ...
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .triganle(let s):
try container.encode(s, forKey: .triangle)
case .rectangle(let s):
try container.encode(s, forKey: .rectangle)
case .circle(let s):
try container.encode(s, forKey: .circle)
}
}
}
类似地,解码的时候,我们可以根据CodingKeys
,还原出CodableShape
:
enum CodableShape: Codable {
/// ...
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let data =
try container.decodeIfPresent(Triangle.self, forKey: .triangle) {
self = .triganle(data)
}
else if let data =
try container.decodeIfPresent(Rectangle.self, forKey: .rectangle) {
self = .rectangle(data)
}
else {
let data = try container.decode(Circle.self, forKey: .circle)
self = .circle(data)
}
}
}
剩下的任务,就是把Shape
和CodableShape
关联起来就好了。我们先给CodableShape
添加一个还原成Shape
的方法。它的实现很简单,就是根据Codable Shape的值,提取出对应case关联的对象:
enum CodableShape: Codable {
/// ...
func toShape() -> Shape {
switch self {
case .triganle(let s):
return s
case .rectangle(let s):
return s
case .circle(let s):
return s
}
}
}
再给Shape
添加一个加工成CodableShape
的方法。它的实现,就是把特定类型的Shape
,装进CodableShape
的case
里:
extension Shape {
func toCodable() -> CodableShape {
if let s = self as? Circle {
return .circle(s)
}
else if let s = self as? Rectangle {
return .rectangle(s)
}
else if let s = self as? Triangle {
return .triganle(s)
}
else {
fatalError("Unrecoginzed shape.")
}
}
}
有了这些准备之后,当我们再要编码[Shape]
的时候,就可以这样:
let shapes =
[Triangle(), Rectangle(), Circle()]
.map { $0.toCodable() }
这样,编码后的Data
对象里,就是CodableShape
。而解码的时候,再像下面这样还原回来就好了:
let decoded = try JSONDecoder().decode(
[Shape.CodableShape].self, from: collData)
decoded
.map { $0.toShape() }
.forEach {
$0.getName()
}
现在,重新执行,getName
就会以多态的方式正常工作了。