Skip to content

Swift 进阶【六】编码和解码

轩辕十四
Published date:

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

概览


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

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

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

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

public typealias Codable = Decodable & Encodable 

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

Encoding


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

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

// 坐标
struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
}
// 地点
struct Placemark: Codable {
    var name: String
    var coordinate: Coordinate
}

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

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 格式。

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

struct Location: Codable {
	  var success: Bool
    var message: String
    var data: LocationData
}

第二层 LocationData

struct LocationData: Codable {
    var LocationList: [LocationItem]
}

第三层 LocationItem

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

JSON 解码

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 包含情况的结构体声明

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
}
Previous
Swift ABI Stability Manifesto
Next
优化 Swift 中 Notification.Name 的使用方式