iOS 学习使用 Swift Codable

Codable 是swift4的新特性, 使用其可以用更少的代码更快的实现数据编解码

JSON

一种基于文本的用于表示信息的格式,人类和计算机都很容易读写这种格式,也是目前使用最广泛的标准

简单写一个json文本

let json = """
{
    "name" : "xiaoming",
    "age" : 18,
    "score" : 99.5

}
""".data(using: .utf8)!
复制代码

我们用上面的json来模拟网络数据请求得到的结果,下面构建model和解析

构建模型

我们创建一种适合这种json表示的swift类型 也就是model, 让json中的值和swift系统中定义类型互相匹配。

  • JSON中的对象是无序键值对,和swift中包含 StringDictionary 类似
  • JSON 将数字表示成一系列数,不受任何语义的影响,它不区分整数和浮点数,不区分定长和非定长数字,也不区分是二进制还是十进制。每个实现自行决定如何解释这些数字。
  • 对于具有 Optional 属性的类型, Codable 可以自动将 null 映射为 nil。
struct Student : Codable {
    var name : String
    var age : Int
    var score : Double
}
复制代码

我们首先定义一个结构体 Student 来对应数据中的顶层对象, 这里我们使用结构体,使用 Class 也无妨。

Codable 协议简介

上面的结构体 Student 遵循了 Codable 协议, 其大大提升了对象和其表示之间相互转换的体验。

理解 Codable 最好的方式就是看它的定义:

typealias Codable = Decodable & Encodable

Codable 是一种 混合类型,由 DecodableEncodable 协议构成。

Decodable 协议定义了一个初始化函数:

init(from decoder: Decoder) throws

遵从 Decodable 协议的类型可以使用任何 Decoder 对象进行初始化。

Encodable 协议定义了一个方法:

func encode(to encoder: Encoder) throws

任何 Encoder 对象都可以创建遵从了 Encodable 协议类型的表示。

只有当一个类型满足这个协议的所有要求的时候,我们才能说这个类型 遵从 那个协议了。 对于 Decodable 而言,唯一的要求就是 init(from:) 初始化方法。

init(from:) 初始化方法接受一个 Decoder 参数。 Decoder 协议需要阐明将 Decodable 对象的表示解码成对象的要求。为了适应各种数据交换格式,解码器和编码器都使用名为 容器(container)的抽象。容器是用来存储值的,可以存储一个值也可以存储多个值,可以像字典一样有键去对应值,也可以像数组一样不需要键。...

因为上面那个 JSON 表示的顶层有一个对象, 所以我们就创建一个有键的容器,然后用不同的键来解码各个属性。

我们创建一个 CodingKeys 枚举,定义属性名称和容器的键之间的映射。这个枚举声明其原始类型是 String,同时声明使用 CodingKey 协议。因为每个名字都和 JSON 中的键相同,所以我们不用为这个枚举提供明确的原始值。

struct Student: Decodable {
    // ...

    private enum CodingKeys: String, CodingKey {
        case name
        case age
        case score
    }
}...
复制代码

接下来,在 init(from:) 初始化函数里我们创建一个键控容器,调用decodercontainer(keyedBy:) 方法,并传入 CodingKeys 作为参数。

最后,我们调用 containerdecode(_:,forKey:) 方法把每个属性初始化一下。

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decode(String.self, forKey: .name)
    self.age = try container.decode(Int.self, forKey: .age)
    self.score = try container.decode(Double.self, forKey: .score)
}...
复制代码

我们有了一个 Student 模型,也遵从了 Decodable 协议,现在我们可以从 JSON 表示中创建一个 student 对象了

把 JSON 解码成模型对象

先引用 Foundation,以便使用 JSONDecoderJSONEncoder

import Foundation

创建一个 JSONDecoder 对象并调用其 decode(_:from:) 方法

let decoder = JSONDecoder()
let student = try! decoder.decode(Student.self, from: json)
复制代码

试验转换成功与否

print(student.name) 
print(student.age) 
print(student.score) 
复制代码
将模型对象编码为 JSON

下面我们实现 Encodable 协议要求的 encode(to:) 方法。 encode(to:)init(from:) 就像一对镜像一样工作。 先调用 encodercontainer(keyedBy:) 方法创建一个 container, 和上一步一样,传入一个 CodingKeys.self 参数。 这里我们把 container 当作一个变量(使用 var 而不是 let), 因为这个方法做的是填充 encoder 的参数,需要进行修改。我们对每个属性调用一次 encode(_:forKey:) 并传入属性的值和它对应的键。...

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(self.name, forKey: .name)
    try container.encode(self.age, forKey: .age)
    try container.encode(self.score, forKey: .score)
}...

复制代码
let encoder = JSONEncoder()
let reencodedJSON = try! encoder.encode(stuent)

print(String(data: reencodedJSON, encoding: .utf8)!)...

复制代码

JSON 对空格和空行(whitespace)不敏感,但是你可能会在意。如果你觉得输出不美观,把 JSONEncoderoutputFormatting 属性设置为 .prettyPrinted 就好了

删除无用的代码

把所有我们写的 Codable 的实现都删掉。这样,应该就只剩下结构和属性的定义了。

struct Student : Codable {
    var name : String
    var age : Int
    var score : Double
}
复制代码

直接运行代码,试试编码和解码 JSON。有什么变化吗?完全没有。 这让我们看到了 Codable 的杀手级特性:

Swift 自动整合了 DecodableEncodable 的一致性。

一种类型只要在其声明中采用协议即可,也就是说,不用在扩展中再做额外的事,只要它的每个属性都有一个符合其协议,其他一切都自动完成了。

尝试解析各种格式的JSON文本

JSON顶层是数组的情况:

    let jsonModels = """
    [
        {
            "name" : "xiaoming",
            "age" : 18,
            "score" : 99.5
        },
        {
            "name" : "daxiong",
            "age" : 19,
            "score" : 66
        }
    ]
    """.data(using: .utf8)!
复制代码

把它解析成 Student 对象的数组十分简单:像之前一样调用 decode(_:from:),把 Student.self 换成 [Student].self 以便去解析 Student 数组而不是单个对象。

let students = try! decoder.decode([Student].self, from: jsonModels)

为什么这么简单就可以了?这要归功于 Swift 4 的另一个新特性:条件一致性。

// swift/stdlib/public/core/Codable.swift.gyb
extension Array : Decodable where Element : Decodable {
    // ...
}

复制代码

我们再在数组的外层套一个字典的形式:


    let jsonComplex = """
    {
        "students" : [
            {
                "name" : "xiaoming",
                "age" : 18,
                "score" : 99.5
            },
            {
                "name" : "daxiong",
                "age" : 19,
                "score" : 66
            }
        ]
    }
    """.data(using: .utf8)!

复制代码

如果 ArrayDictionary 包含的 KeyTypeValueType 都遵从 Decodable 协议,那么这样的数组或字典本身也就遵从了 Decodable 协议:

// swift/stdlib/public/core/Codable.swift.gyb
extension Dictionary : Decodable where Key : Decodable, 
                                       Value : Decodable {
    // ...
}...
复制代码

因此你可以把之前 decode(_:from:) 的调用参数直接换成 [String: [Student]].self (Dictionary<String, Plane> 的简写):

        let jsonData = try! decoder.decode([String: [Student]].self, from: jsonComplex)
        let students = jsonData["students"]
复制代码

或者你也可以创建一个遵从 Decodable 协议的新类型,然后给这个类型一个属性来放 [Student] ,该属性的名字应和 JSON 中的键名相对应,再把这个新类型传入 decode(_:from:) 函数中:

struct Brige: Decodable {
    var students: [Student]
}

let Brige = try! decoder.decode(Brige.self, from: json)
let students = Brige.students...
复制代码
  • Codable 是一种 混合类型,由 DecodableEncodable 协议构成。
  • 只要一种类型中的每个属性都遵从协议,在类型定义时声明一下,Swift 就会自动整合 DecodableEncodable
  • 如果 ArrayDictionary 中的每一个元素都遵从 Codable 类型,那么它们本身也会遵从 Codable

转载于:https://juejin.im/post/5bfa8cc56fb9a049a42eb68f

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值