我们先定义一组用于演示的支持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就会以多态的方式正常工作了。

image