前言

Swift 4.0版本引入了一种新的对象序列化的方式Codeable,用于代替原先OC语法的NSCode协议。 在程序执行过程中,我们经常需要通过网络发送数据,保存数据到磁盘,这往往是一个对象序列化的过程;在Swift4.0开始,系统提供一套对象编解码的协议,可以自动或者自定义的实现对象的序列化。

typealias Codable = Decodable & Encodable

自动解码和编码

想要对象可编码,最简单的方式就是用可编码的类型去声明属性;

为了描述简单,结构体和对象都描述为对象

这些可编码的属性包括:Int String Double Date Data URL

struct Landmark {
    var name: String
    var foundingYear: Int
}

接下来,我们只需要让对象实现Codeable协议,该对象就自动实现了编码和解码。

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    
    // Landmark now supports the Codable methods init(from:) and encode(to:), 
    // even though they aren't written as part of its declaration.
}

但是我们平常开发中,属性往往也是自定义对象,在Codeable协议中,只需要所有的属性都支持编解码,那么该对象也能编解码。

struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
}

struct Landmark: Codable {
    // Double, String, and Int all conform to Codable.
    var name: String
    var foundingYear: Int
    
    // Adding a property of a custom Codable type maintains overall Codable conformance.
    var location: Coordinate
}

内置的类型,比如Array Dictionary,只需要元素实现了Codeable,那么也支持编解码。

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    
    // Landmark is still codable after adding these properties.
    var vantagePoints: [Coordinate]
    var metadata: [String: String]
    var website: URL?
}

手动解码和编码

如果你对象和编码结构不同,你可以自定义编码和解码协议的实现,完成对象的编码和解码。

struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double

    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case additionalInfo
    }
    
    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
    }
}

解码实现init(from decoder: Decoder)

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
        
        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
    }
}

编码实现 encode(to encoder: Encoder)

extension Coordinate: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
        
        var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        try additionalInfo.encode(elevation, forKey: .elevation)
    }
}

JSONEncoder & JSONDecoder

Swift提供了系统的JSON数据的编解码方式,可以将实现Codeable对象和JSON对象相互转化,非常的简单和方便。

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)

/* Prints:
 {
   "name" : "Pear",
   "points" : 250,
   "description" : "A ripe pear."
 }
*/
struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

let json = """
{
    "name": "Durian",
    "points": 600,
    "description": "A fruit with a distinctive scent."
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let product = try decoder.decode(GroceryProduct.self, from: json)

print(product.name) // Prints "Durian"

PS

我自己写的一个KV存储库SwiftLvDB,也将支持Codeable对象存储,敬请关注。

参考

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types https://developer.apple.com/documentation/foundation/jsonencoder https://developer.apple.com/documentation/foundation/jsondecoder