Swift 进阶【六】编码和解码

将程序内部的数据结构序列化为一些可交换的数据格式,以及反过来将通用的数据格式反序列化为内部使用的数据结构,这在编程中是一项非常常见的任务。Swift 将这些操作称为编码(encoding)和解码(decoing)。Swift 4 的一个主要特性就是定义了一套标准的编码和解码数据的方法,所有的自定义类型都能选择使用这套方法。

概览


Codable 系统(以其基本“协议”命名,而这个协议其实是一个类型别名)的设计主要围绕三个核心目标;

  • 普遍性 — 它对结构体,枚举和类都适用。
  • 类型安全 — 像是 JSON 这样的可交换格式通常都是弱类型,而你的代码应该要使用强类型数据。
  • 减少模板代码 — 在让自定义类型加入这套系统时,应该让开发者尽可能少地写重复的“适配代码”。编译器应该为你自动生成这些代码。

某个类型通过声明自己遵守 Encodable 和/或 Decodable 协议来表明自己具备被序列化和/或反序列化的能力。这两个协议各自只有一个必须实现的方法 - Encodable 定义了 encode(to:) 用来对值自身进行编码,Decodable 指定了一个初始化方法,来从序列化的数据中创建实例:

1
2
3
4
5
6
7
8
9
10
/// 某个类型可以将自身编码为一种外部表示。
public protocol Encodable {
/// 将值编码到给定的 encoder 中。
public func encode(to encoder: Encoder) throws
}
/// 某个类型可以从外部表示中解码得到自身。
public protocol Decodable {
/// 通过从给定的 decoder 中解码来创建新的实例。
public init(from decoder: Decoder) throws
}

因为大多数实现了其中一个协议的类型,也会实现另一个,所以标准库中还提供了 Codable 类型别名,来作为这两个协议组合后的简写:

1
public typealias Codable = Decodable & Encodable 

标准库中包括 Bool,数值类型和 String 等所有基本类型,都直接是 Codable 类型。那些含有 Codable 元素的可选值,数组,字典和集合,也都满足 Codable。最后,包括 DataDateURLCGPointCGRect 在内的许多 Apple 框架中的常用数据类型,也已经适配了 Codable。

Encoding


因为 JSON 是最常见的格式,所以我们来集中研究一下 JSONEncoderJSONDecoder

我们先来建两个结构体,一个是封装了坐标的结构体,一个是封装了地点的结构体。

1
2
3
4
5
6
7
8
9
10
// 坐标
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
}
// 地点
struct Placemark: Codable {
var name: String
var coordinate: Coordinate
}

接下来我们可以将一个 Placemark 数组编码为 JSON 格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let places = [
Placemark(name: "Berlin", coordinate:
Coordinate(latitude: 52, longitude: 13)),
Placemark(name: "Cape Town", coordinate:
Coordinate(latitude: -34, longitude: 18))
]

do {
let encoder = JSONEncoder()
let jsonData = try encoder.encode(places) // 129 bytes
let jsonString = String(decoding: jsonData, as: UTF8.self)
/*
[{"name":"Berlin","coordinate":{"longitude":13,"latitude":52}},
{"name":"Cape Town","coordinate":{"longitude":18,"latitude":-34}}]
*/
} catch {
print(error.localizedDescription)
}

Decoding


这一次我们来看一个复杂一点的,但是在实际应用中,能够经常见到的 JSON 格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let jsonStr = 
"""
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
"""

这里有三层结构,第一层是最外面的一层,第二层是 data 的这一层,第三层是 LocationList 对应的这一层,这一层是一个数组。

下面我们来建立三个结构体,分别对应着三层结构:

第一层 Location

1
2
3
4
5
struct Location: Codable {
var success: Bool
var message: String
var data: LocationData
}

第二层 LocationData

1
2
3
struct LocationData: Codable {
var LocationList: [LocationItem]
}

第三层 LocationItem

1
2
3
4
struct LocationItem: Codable {
var LocID: Int
var LocName: String
}

JSON 解码

1
2
3
4
5
6
7
8
9
let jsonDecode = JSONDecoder()
if let jsonData = jsonStr.data(using: .utf8) {
do {
let decoded = try jsonDecode.decode(Location.self, from: jsonData)
print(decoded.data.LocationList.first?.LocName ?? "")
} catch let error {
print(error.localizedDescription)
}
}

一种更容易看出 JSON 包含情况的结构体声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Location: Codable {

struct LocationData: Codable {

struct LocationItem: Codable {
var LocID: Int
var LocName: String
}

var LocationList: [LocationItem]
}

var success: Bool
var message: String
var data: LocationData
}