概述

序列化(serialization),是指把方针状况转化成能够存储或传输的方式。反序列化(deserialization)则是它的逆进程。

方针状况典型的如实例方针,存储或传输的方式常以字节序列/二进制数据存在,能够序列化和反序列化的进程可简略用公式表明为:

方针⇌反序列化序列化二进制数据{\text{方针} \mathop{ \rightleftharpoons }\limits_{{\text{反序列化}}}^{{\text{序列化}}}\text{二进制数据}}

而一些更容易了解的概念,如加密、持久后,则是在这个进程之前或之后的处理进程。

每种序列化协议都存在优缺点,在规划之初有自己共同的应用场景,客户端对序列化协议的选型一般考量:

  • 通用性:技能层面跨平台、跨语言。
  • 健壮性。
  • 可读性。
  • 功用。
  • 兼容性、扩展性。
  • 安全性。

根据以上到要素到考量,iOS 中常用的序列化计划有:

  1. 根据 NSCodingNSSecureCoding 的归档计划。
  2. XML 协议。
  3. JSON 协议计划。
  4. Protocol Buffers 协议计划。

以上计划的区别是根据序列后的数据格局区其他。

一些教科书上会讲到“计算机文件根本上分为两种:二进制文件和纯文本文件。”尽管这种说法有些胡扯,毕竟从物理视点上来说最终存储成二进制数据,仅仅说纯文本文件是根据字符编码罢了,而所谓的排除纯文本文件的“二进制文件”则是自界说编码。测验从编码视点能够再次对上述计划进行区别:

  • 方针 <-> 二进制数据:归档、Protocol Buffers。
  • 方针 <-> 文本 <-> 二进制数据:XML、JSON。

前三种有 iOS 的原生支撑,本文内容仅讨论前三种的完结应用。

原生支撑

在 Swift 中,规范库界说了 EncodableDecodableCodable,以及 EncoderDecoder API 来执行编码宽和码,参阅 Encoding, Decoding, and Serialization。Foundation 经过 EncodableWithConfigurationDecodableWithConfiguration 协议进行扩展,用于需求额外静态信息进行编码宽和码的类型,如 AttributedString

Objective-C 中,NSCoding 界说来方针进行编码宽和码的协议。在向自界说类型增加序列化时,还应遵从 NSSecureCoding 协议,该协议增加了对作为解码进程的一部分实例化任意方针引入的安全漏洞的保护。

许多体系结构运用这些类型。在运用外部体系(例如 URL 端点)时,运用 JSON 和 XML API 将应用程序的类型序列化为规范格局。

NSCoding 主要的约束是需求依靠 Foundation,且需求承继于 NSObject,而 Codable 就不需求,但 Codable 仅支撑 Swift,Objective-C 上不能运用,另外 Codable 功用也更灵敏,能清松吧模型转化成 JSON 或 XML。

根本功用

  • 根本转化:
    • json <-> model
    • plist <-> model
    • dictionary <-> model
  • 自界说 key mapping
  • 值处理

计划:

  • NSCoding
  • Serialization
  • Codable
  • YYModel
  • JSONModel
  • SwiftyJSON
  • ObjectMapper
  • BDModel
  • Mantle

内容:

  • 原理、界说、支撑功用
  • 运用场景
  • 约束
  • 根本运用

NSCoding + NSCoder

NSCoding 协议界说了编码宽和码的办法。为归档(把方针和其他结构存储到磁盘中)和分发(把方针复制到不同的地址空间)供给根底。根据面相方针规划原则,由遵从 NSCoding 协议的方针进行编码宽和码。

# 解码。从 coder 数据构建自身。
init?(coder: NSCoder)
# 编码。将自身编码到 coder。
func encode(with coder: NSCoder)

NSCoding 协议用于自界说的模型,声明要编解码那些 key,NSCoder 详细子类负责将详细的模型方针序列化为 Data,或将 Data 反序列化为模型方针。

在 iOS 12.0+ 中,苹果推荐运用 NSSecureCoding 协议,这个协议在 NSCoding 的根底上,还需求完结一个静态的特点:

static var supportsSecureCoding: Bool { true }

NSSecureCoding 的安全主要体现在增加了类型安全的校验,保证数据仅能被源编码类型解码。

名词解释:

  • 归档(archiving):把方针和其他结构存储到磁盘中。
  • 分发(distribution):把方针复制到不同的地址空间。

编解码器(coder。编解码器在音视频范畴是 codec)可分为编码器(encoder,只能解码)宽和码器(decoder,只能编码)。

  • 归档(archive):可了解为编码、序列化进程。
  • 解档(unarchive):可了解为解码、反序列化进程。

NSCoder 抽象类,作为支撑归档和分发方针的根底,声明晰详细子类在内存和其他格局之间传输方针和值的接口,即界说了编解码器的接口。

NSCoder 对方针、标量、C 数组、结构体、字符串以及指向这些类型的指针进行操作。它不处理完结因平台而异的类型,例如联合体、无类型指针(void *)、函数指针和指针长链。编码器将方针类型信息与数据一起存储,因此从字节省解码的方针通常与最初编码到流中的方针归于同一类型。但是,方针能够在编码时更改其类型。

详细用于实例化子类为:

  • NSArchiver:编码器。弃用,macOS 10.0–10.13。
  • NSUnarchiver:解码器。弃用,macOS 10.0–10.13。
  • NSKeyedArchiver:键控编码器。iOS 2.0+,macOS 10.2+。
  • NSKeyedUnarchiver:键控解码器。iOS 2.0+,macOS 10.2+。
  • NSPortCoder:分布式方针体系中传输方针(署理)的编解码器。弃用,macOS 10.0–10.13。

当然也支撑自己完结一个 NSCoder 子类,详细可参阅 Archives and Serializations Programming Guide 中的 Subclassing NSCoder。

上述的详细编解码器编码的方针产品宽和码的来源数据都是表明二进制数据的 NSData/Data。该二进制数据与架构无关,可直接进行归档和分发。

给定用于编码值的 key 有必要在当时编码方针的范围内保持仅有,即一个类型中,每个特点的编码 key 是仅有的。

键控存档与非键控存档的不同之处在于,编码到存档中的一切方针和值都有称号或键。解码时有必要运用编码时运用的 key。解码键控存档时,解码器按 key 恳求值,意味着能够不按次序编解码,同时也为向前和向后兼容性供给了更好的支撑。这也是为什么 Apple 要废弃掉 NSArchiverNSUnarchiver 的原因。

为更好地支撑归档类型,在 macOS 10.2 及更高版别中,首选键控归档(keyed archiving)。

无脑选择:NSSecureCoding + 键控归档/解档。

运用场景

Model: NSObject <-> Data

优势:

  • 线程安全。
  • 直接编码成 Data,有一定安全性,同时也难以从 Data 产品中排查问题。
  • NSSecureCoding 支撑类型检查,愈加安全。

约束:

  • 遵从协议的类需是 NSObject 的子类。
  • 不支撑自界说 key。
  • 只能序列化为 Data,并从 Data 反序列化为模型方针。

API 剖析

NSCoder

用于 NSCoding 中编解码的 NSCoder API,都存在一一对应的联系:

# 还有一系列不同类型的重载函数,函数签名只有 value 的类型不一样
func encode(
    _ value: Bool,
    forKey key: String
)
# 还有一系列详细类型的 decodeXxx(forKey:) 办法用于解码详细的类型
func decodeBool(forKey key: String) -> Bool
# 关于 Swift 的类型,可直接运用该办法
func decodeObject(forKey key: String) -> Any?
func decodeObject<DecodedObjectType>(
    of cls: DecodedObjectType.Type,
    forKey key: String
) -> DecodedObjectType? where DecodedObjectType : NSObject, DecodedObjectType : NSCoding
@nonobjc func decodeObject(
    of classes: [AnyClass]?,
    forKey key: String
) -> Any?
@nonobjc func decodeTopLevelObject(forKey key: String) throws -> Any?
@nonobjc func decodeTopLevelObject(
    of classes: [AnyClass]?,
    forKey key: String
) throws -> Any?
func decodeTopLevelObject<DecodedObjectType>(
    of cls: DecodedObjectType.Type,
    forKey key: String
) throws -> DecodedObjectType? where DecodedObjectType : NSObject, DecodedObjectType : NSCoding
# 字节 buffer
func encodeBytes(
    _ bytes: UnsafePointer<UInt8>?,
    length: Int,
    forKey key: String
)
func decodeBytes(
    forKey key: String,
    returnedLength lengthp: UnsafeMutablePointer<Int>?
) -> UnsafePointer<UInt8>?
# Objective-C 数组,需指定元素类型。
func encodeArray(
    ofObjCType type: UnsafePointer<CChar>,
    count: Int,
    at array: UnsafeRawPointer
)
func decodeArray(
    ofObjCType itemType: UnsafePointer<CChar>,
    count: Int,
    at array: UnsafeMutableRawPointer
)

decodeObject(of:forKey:) 调用需求求该类型遵从 NSSecureCoding,否则会失败(failWithError(_:))。

  • requiresSecureCodingtrue 时,会检查本类或其超类与入参的 cls 是否匹配,不匹配也失败。
  • requiresSecureCodingfalse 时,会疏忽 cls 入参。

decodeObject(of:forKey:) 重载支撑了传入一组类型,运用规矩相似,差异仅仅能够传入多个候选类型。

相比照 decodeObject(forKey:) 则适用于类型仅仅遵从了 NSCoding,或 NSSecureCodingrequiresSecureCodingfalse 的情况。

NSKeyedArchiver/NSKeyedUnarchiver

NSKeyedArchiverNSKeyedUnarchiver 咱们常用其类办法一步到位完结归档宽和档:

# NSKeyedArchiver 归档。iOS 11.0+。
class func archivedData(
    withRootObject object: Any,
    requiringSecureCoding requiresSecureCoding: Bool
) throws -> Data
# NSKeyedArchiver 归档。iOS 2.0–12.0。
class func archivedData(withRootObject rootObject: Any) -> Data
# iOS 2.0–12.0。
class func unarchiveObject(with data: Data) -> Any?
class func unarchiveObject(withFile path: String) -> Any?
# NSKeyedUnarchiver 解档。iOS 11.0+。
@nonobjc static func unarchivedObject<DecodedObjectType>(
    ofClass cls: DecodedObjectType.Type,
    from data: Data
) throws -> DecodedObjectType? where DecodedObjectType : NSObject, DecodedObjectType : NSCoding
class func unarchivedObject(
    ofClasses classes: Set<AnyHashable>,
    from data: Data
) throws -> Any
@nonobjc static func unarchivedObject(
    ofClasses classes: [AnyClass],
    from data: Data
) throws -> Any?

NSKeyedUnarchiverunarchivedObject(ofClass:from:) 要求 Data 是由 NSSecureCoding 的类型实例编码而成的,否则会失败。

小结

可见,上述 API 中需求传入类型参数的都是面相遵从 NSSecureCoding 的方针,否则直接在运行时抛出过错。

实践

以下示例中展示了 NSCodingNSSecureCodingNSKeyedArchiverNSKeyedUnarchiver 的详细运用。

class Song: NSObject {
    init(artist: String, title: String, assetURL: URL, discNumber: Int) {
        self.artist = artist
        self.title = title
        self.assetURL = assetURL
        self.discNumber = discNumber
    }
    var artist: String
    var title: String
    var assetURL: URL
    var discNumber: Int
}
extension Song {
    enum CodingKeys: String, CodingKey {
        case artist, title, assetURL, discNumber
    }
}
extension NSCoder {
    func encode(_ value: Any, key: Song.CodingKeys) {
        encode(value, forKey: key.rawValue)
    }
    func decode<T>(of: T.Type? = nil, key: Song.CodingKeys) -> T? {
        decodeObject(forKey: key.rawValue) as? T
    }
    func decodeSecurely<T: NSCoding & NSObject>(of: T.Type? = nil, key: Song.CodingKeys) -> T? {
        decodeObject(of: T.self, forKey: key.rawValue)
    }
}
// MARK: NSCoding
class CodingSong: Song, NSCoding {
    func encode(with coder: NSCoder) {
        // 运用 @objc 可运用 #keyPath(artist) 生成字符串
        // @objc var artist: String
        // coder.encode(artist, forKey: #keyPath(artist))
        coder.encode(artist, key: .artist)
        coder.encode(title, key: .title)
        coder.encode(assetURL, key: .assetURL)
        coder.encode(discNumber, key: .discNumber)
    }
    required convenience init?(coder: NSCoder) {
        let artist: String = coder.decode(key: .artist)!
        let title: String = coder.decode(key: .title)!
        let assetURL: URL = coder.decode(key: .assetURL)!
        let discNumber: Int = coder.decode(key: .discNumber)!
        self.init(artist: artist, title: title, assetURL: assetURL, discNumber: discNumber)
    }
}
// MARK: NSSecureCoding
class SecureCodingSong: CodingSong, NSSecureCoding {
    required convenience init?(coder: NSCoder) {
        let artist: NSString = coder.decodeSecurely(key: .artist)!
        let title: NSString = coder.decodeSecurely(key: .title)!
        let assetURL: NSURL = coder.decodeSecurely(key: .assetURL)!
        let discNumber: NSNumber = coder.decodeSecurely(key: .discNumber)!
        self.init(artist: artist as String, title: title as String, assetURL: assetURL as URL, discNumber: discNumber.intValue)
    }
    static var supportsSecureCoding: Bool { true }
}
class CommonCodingSong: Song, NSSecureCoding {
    func encode(with coder: NSCoder) {
        coder.encode(artist, key: .artist)
        coder.encode(title, key: .title)
        coder.encode(assetURL, key: .assetURL)
        coder.encode(discNumber, key: .discNumber)
    }
    required convenience init?(coder: NSCoder) {
        if Self.supportsSecureCoding {
            let artist: NSString = coder.decodeSecurely(key: .artist)!
            let title: NSString = coder.decodeSecurely(key: .title)!
            let assetURL: NSURL = coder.decodeSecurely(key: .assetURL)!
            let discNumber: NSNumber = coder.decodeSecurely(key: .discNumber)!
            self.init(artist: artist as String, title: title as String, assetURL: assetURL as URL, discNumber: discNumber.intValue)
        } else {
            let artist: String = coder.decode(key: .artist)!
            let title: String = coder.decode(key: .title)!
            let assetURL: URL = coder.decode(key: .assetURL)!
            let discNumber: Int = coder.decode(key: .discNumber)!
            self.init(artist: artist, title: title, assetURL: assetURL, discNumber: discNumber)
        }
    }
    static var supportsSecureCoding: Bool { true }
}
func testCoding() {
    print("testing NSCoding")
    typealias Song = CodingSong
    let song = Song(artist: "全能青年旅馆", title: "冀西南林路行", assetURL: URL(fileURLWithPath: "./song.mp3"), discNumber: 666)
    do {
        // 归档
        let data = try NSKeyedArchiver.archivedData(withRootObject: song, requiringSecureCoding: false)
        print("ecoded data: \(data)")
        // 解档
        let decodedSong = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
        dump(decodedSong, name: "DecodedSong")
    } catch {
        print(error)
    }
}
func testSecureCoding() {
    print("testing NSSecureCoding")
    typealias Song = SecureCodingSong
    let song = Song(artist: "全能青年旅馆", title: "冀西南林路行", assetURL: URL(fileURLWithPath: "./song.mp3"), discNumber: 666)
    do {
        // 归档
        let data = try NSKeyedArchiver.archivedData(withRootObject: song, requiringSecureCoding: true)
        print("ecoded data: \(data)")
        // 解档
        // 这儿要求都需求 SecureCoding
        let decodedSong = try NSKeyedUnarchiver.unarchivedObject(ofClass: Song.self, from: data)
        dump(decodedSong, name: "DecodedSong")
    } catch {
        print(error)
    }
}
func testCodec() {
    testCoding()
    testSecureCoding()
}

实用技巧:

  • 由于 NSCoding 完结中需求按 key 编码宽和码数据,这要求编码宽和码运用的 key 需求保持共同。这儿经过界说了一组 CodingKeys 枚举来保证 key 的复用。
  • 除了上述 CodingKeys 枚举的方式复用 key,也能够运用 @objc 润饰特点(地点的类需为 NSObject 子类),然后运用 #keyPath(特点) 的方式生成字符串。
  • 这儿为了缩写 API 和简化 CodingKeys 枚举的运用,编写了几个 NSCoder 扩展办法,让代码逻辑愈加简练清晰。

留意:

  • 上述的 CodingSong 类完结了 NSCodingSecureCodingSong 类完结了 NSSecureCoding,也能够像 CommonCodingSong 那样直接支撑两种协议。但根据安全性考虑,仍是建议大家仅完结 NSSecureCoding
  • 如在 CommonCodingSong 中,能够经过自身的 supportsSecureCoding 特点控制自身是完结 NSCoding 仍是 NSSecureCoding
  • NSCodingNSSecureCoding 在编码的 API 都是共同的,只有在解码的时分才需求调用不同的 API。

也能够把 object 关联到 key 中,即手动完结 encode/decode:

let person = Person(firstName: "Bq", lastName: "Lin", age: 18)
let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("person_bq.bin")
let key = "bq"
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(person, forKey: key)
archiver.finishEncoding()
try! archiver.encodedData.write(to: url)
let data = try! Data(contentsOf: url)
let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: data)
let obj = unarchiver.decodeObject(of: Person.self, forKey: key)!
dump(obj)

更多资料

  • Archives and Serializations Programming Guide

Serialization

JSONSerializationPropertyListSerialization。故名思义,这两个类是 Foundation 原生供给的用于 JSON 和 plist 序列化和反序列化的完结。即使后面讲述的 Codable 是独立于体系的 Swift 完结,但在 Apple 的操作体系中,仍是会运用这两个类进行 JSON 和 plist 的转化。

JSONSerialization

运用 JSONSerialization 来完结 object <-> JSON Data。object 约束为:

  • 除非运用 fragmentsAllowed 特点,否则顶层有必要是 NSArrayNSDictionary
  • 一切方针必需是 NSStringNSNumberNSArrayNSDictionaryNSNull 方针。
  • 字典 key 需为 NSString 方针。
  • 数值不应为 NaN 或无穷。

能够调用 isValidJSONObject(_:) 进行校验或测验进行转化。

API 接口:

# 校验,否则直接抛出异常,运用 try 无法捕获。
class func isValidJSONObject(_ obj: Any) -> Bool
# 序列化方针
class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
class func jsonObject(with stream: InputStream, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
# 反序列化 Data
class func data(withJSONObject obj: Any, options opt: JSONSerialization.WritingOptions = []) throws -> Data
class func writeJSONObject(_ obj: Any, to stream: OutputStream, options opt: JSONSerialization.WritingOptions = [], error: NSErrorPointer) -> Int

留意:序列化前,需对输入的方针调用 isValidJSONObject(_:) 进行校验,对不符合上述要求的数据将回来 false,否则 JSONSerialization 直接抛出异常,运用 try 都无法捕获。

由于 JSON 格局原本没有类型校验,所以在序列化/反序列化进程中方针都是 Any 类型,运用进程中还需求用户再进行类型转化。

PropertyListSerialization

运用 PropertyListSerialization 来完结 array/dictionary <-> plist data。

  • 顶层是 array/dictionary;
  • 数据只能是 NSDataNSStringNSArrayNSDictionaryNSDateNSNumber 方针。
  • plsit 能 Toll-Free Bridging 到 Core Foundation 类型。

API 接口:

# 序列化 plist
class func data(fromPropertyList plist: Any, format: PropertyListSerialization.PropertyListFormat, options opt: PropertyListSerialization.WriteOptions) throws -> Data
class func writePropertyList(_ plist: Any, to stream: OutputStream, format: PropertyListSerialization.PropertyListFormat, options opt: PropertyListSerialization.WriteOptions, error: NSErrorPointer) -> Int
# 反序列化 plist
class func propertyList(from data: Data, options opt: PropertyListSerialization.ReadOptions = [], format: UnsafeMutablePointer<PropertyListSerialization.PropertyListFormat>?) throws -> Any
class func propertyList(with stream: InputStream, options opt: PropertyListSerialization.ReadOptions = [], format: UnsafeMutablePointer<PropertyListSerialization.PropertyListFormat>?) throws -> Any
# 校验
class func propertyList(_ plist: Any, isValidFor format: PropertyListSerialization.PropertyListFormat) -> Bool

运用场景

NSArray/NSDictionary <-> JSON data、plsit data。

优势:

  • 线程安全。

约束:

  • 不能直接转化详细的类,只能是些根本值,关于详细的类,需转化成数组或字典。
  • 尽管是体系自带,但功用不一定是最佳,当遇到功用瓶颈,可考虑替换这两个类的完结。

Codable + Encoder/Decoder

Swift only,与上面的 NSCoding 类型不同,Coable 可协议用于一切的自界说的类型。能够把自身类型转化为外部表明的类型(如 JSON、plist)。遵从 Encoder/Decoder 协议的类负责完结详细的编码宽和码/序列化和反序列详细逻辑。目前 Foundation 供给的编解码器有:

  • PropertyListEncoderPropertyListDecoder
  • JSONEncoderJSONDecoder

当然其内部仍是运用 JSONSerializationPropertyListSerialization 来做详细的转化。

大多数内置的 Swift 类型都默许支撑 Codable。自界说类型(class、struct、enum)只需其一切特点都遵从 Codable 类型,声明为 Codable,不必编写任何额外代码。enum 特别点,只需有声明晰原始类型,才能声明为 Codable

能声明为 Codable 类型,就能够主动序列化为以特点称号为 key 的 JSON、plist 等二进制数据,或从中反序列为详细的 Codable 类型。

自界说才能:

  • 特点类型声明为 Optional,能够用于表明可能不存在的特点。
  • 界说嵌套类型 enum CodingKeys: String, CodingKey 能够自界说特点名到编码 key 的映射。case 名为特点名,rawValue 为对应的编码 key。
  • 自界说 Codable 的两个办法能够自界说编解码的一切细节。

API 接口

typealias Codable = Decodable & Encodable
public protocol Encodable {
    func encode(to encoder: Encoder) throws
}
public protocol Decodable {
    init(from decoder: Decoder) throws
}
public protocol Encoder {
    var codingPath: [CodingKey] { get }
    var userInfo: [CodingUserInfoKey : Any] { get }
    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey
    func unkeyedContainer() -> UnkeyedEncodingContainer
    func singleValueContainer() -> SingleValueEncodingContainer
}
public protocol Decoder {
    var codingPath: [CodingKey] { get }
    var userInfo: [CodingUserInfoKey : Any] { get }
    func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
    func unkeyedContainer() throws -> UnkeyedDecodingContainer
    func singleValueContainer() throws -> SingleValueDecodingContainer
}

运用场景

Model: Any/[Model] <-> JSON/plist/… Data

优势:

  • 一步到位从 Model 到 JSON/plist/… Data。
  • 灵敏,各个阶段支撑自界说。

约束:

  • 类承继时,需求自界说 Codable 的两个办法,否则只会编解码当时类界说的特点,不会承继父类的特点。
  • 尽管支撑自界说序列化进程,但仍是不够易用。

运用

根本运用:

// 不需求写 encode(with:) 和 init(coder:) 的协议办法
// 由于协议扩展 extension Codable 中供给了默许完结
class Person: NSObject, Codable {
    var firstName: String
    var lastName: String
    var age: Int
    override var descirption: String {
        return "\(self.firstName) \(self.lastName) \(age)"
    }
    init(firstName: String, lastName: String, age: Int) {
        self.firstName = firstName
        self.lastName = lastName
        self.age = age
    }
}
// 编解码
let docsurl = try FileManager.default.url(for: .docmentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let filePath = docsurl.appendingPathComponent("person.txt")
let person = Perosn(firstName: "yuuki", lastName: "lili", age: 20)
// 写入
let encodedPerson = try PropertyListEncoder().encode(person)
encodedPerson.write(to: filePath, options: .atomic)
// 读取
let contents = try Data(contentOf: filePath)
let decodedPerson = try PropertyListDecoder().decode(Person.self, from: contents)
// "yuuki lili 20"
print(decodedPerson)

运用 Codable 存储 NSCoding 数据。由于 Cocoa 中很多类仅仅遵从了 NSCoding 协议,而不是 Codable 协议,所以会有两者一起运用的场景。运用战略是经过 Data 来作为中间的桥梁。

struct Person {
    var name: String
    var favoriteColor: UIColor // NSCoding
}
extension Person: Codable {
    // 由于咱们需求显示的声明编码宽和码的内容,因此需求在这儿写出 CodingKeys
    enum CodingKeys: String, CodingKey {
        case name
        case favoriteColor
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // 字符串类型,直接解码
        name = try container.decode(String.self, forKey: .name)
        let colorData = try container.decode(Data.self, forKey: .favoriteColor)
        // NSCoding 方式
        favoriteColor = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(colorData) as? UIColor ?? UIColor.black
    }
    func encode(to encoder: Encoder) throws {
        var container = try encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        // NSCoding 方式
        let colorData = try NSKeyedArchiver.archivedData(withRootObject: favoriteColor, requiringSecureCoding: false)
        try container.encode(colorData, forKey: .favoriteColor)
    }
}
let taylor = Person(name: "Taylor Swift", favoriteColor: .blue)
let encoder = JSONEncoder()
let decoder = JSONDecoder()
do {
    // 编码
    let encoded = try encoder.encode(taylor)
    // 解码
    let person = try decoder.decode(Person.self, from: encoded)
    print(person.favoriteColor, person.name)
} catch {}

自界说调集类型的编解码:

 struct Student: Codable {
    let scores: [Int] = [66, 77, 88]
    enum CodingKeys: String, CodingKey {
        case scores
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        // 创立一个对数组处理用的容器 (UnkeyedEncdingContainer)
        var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .scores)
        // 处理后序列化
        try scores.forEach {
            try unkeyedContainer.encode("\($0) 分")
        }
    }
}
let res = """
{
    "gross_score": 120,
    "scores": [
        0.65,
        0.75,
        0.85
    ]
}
"""
struct Student: Codable {
    let grossScore: Int
    let scores: [Float]
    enum CodingKeys: String, CodingKey {
        case grossScore = "gross_score"
        case scores
    }
    init(grossScore: Int, scores: [Float]) {
        self.grossScore = grossScore
        self.scores = scores
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let grossScore = try container.decode(Int.self, forKey: .grossScore)
        var scores = [Float]()
        // 处理数组时所运用的容器 (UnkeyedDecodingContainer)
        var unkeyedContainer = try container.nestedUnkeyedContainer(forKey: .scores)
        // isAtEnd:A Boolean value indicating whether there are no more elements left to be decoded in the container.
        while !unkeyedContainer.isAtEnd {
            let proportion = try unkeyedContainer.decode(Float.self)
            let score = proportion * Float(grossScore)
            scores.append(score)
        }
        self.init(grossScore: grossScore, scores: scores)
    }
}

处理派生类方针时,需求自界说完结 Codable 办法:

class Ponit2D: Codable {
    var x = 0.0
    var y = 0.0
    // 标记为 private
    private enum CodingKeys: String, CodingKey {
        case x
        case y
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(x, forKey: .x)
        try container.encode(y, forKey: .y)
    }
}
// 1
class Ponit3D: Ponit2D {
    var z = 0.0
    // 标记为 private
    private enum CodingKeys: String, CodingKey {
        case z
    }
    override func encode(to encoder: Encoder) throws {
        //调用父类的 encode 办法将父类的特点 encode
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(z, forKey: .z)
    }
}
//{
//  "x" : 0,
//  "y" : 0,
//  "z" : 0
//}
// 2
class Ponit3D: Ponit2D {
    var z = 0.0
    private enum CodingKeys: String, CodingKey {
        case z
    }
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        // 创立一个供给给父类 encode 的容器来区别父类特点和派生类特点
        try super.encode(to: container.superEncoder())
        try container.encode(z, forKey: .z)
    }
}
//{
//    "super" : {
//        "x" : 0,
//        "y" : 0
//    },
//    "z" : 0
//}
// 3
class Ponit3D: Ponit2D {
    var z = 0.0
    private enum CodingKeys: String, CodingKey {
        case z
        case point2D //用于父类特点容器的 key 名
    }
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        // 创立一个供给给父类 encode 的容器来区别父类特点和派生类特点,并将 key 设为 point2D
        try super.encode(to: container.superEncoder(forKey: .Point2D))
        try container.encode(z, forKey: .z)
    }
}
//{
//    "point2D" : {
//        "x" : 0,
//        "y" : 0
//    },
//    "z" : 0
//}

兼容多个版别 API 的模型,运用 userInfo 存储选项:

// version1
{
    "time": "Nov-14-2017 17:25:55 GMT+8"
}
// version2
{
    "time": "2017-11-14 17:27:35 +0800"
}

在 encoder.userInfo 中存储 CodingUserInfoKey 类型的自界说 key 来选择格局。

struct VersionController {
    enum Version {
        case v1
        case v2
    }
    let apiVersion: Version
    var formatter: DateFormatter {
        let formatter = DateFormatter()
        switch apiVersion {
        case .v1:
            formatter.dateFormat = "MMM-dd-yyyy HH:mm:ss zzz"
            break
        case .v2:
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
            break
        }
        return formatter
    }
    static let infoKey = CodingUserInfoKey(rawValue: "dateFormatter")!
}
func encode<T>(of model: T, optional: VersionController? = nil) throws where T: Codable {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    if let optional = optional {
        // 经过 userInfo 存储版别信息
        encoder.userInfo[VersionController.infoKey] = optional
    }
    let encodedData = try encoder.encode(model)
    print(String(data: encodedData, encoding: .utf8)!)
}
struct SomeThing: Codable {
    let time: Date
    enum CodingKeys: String, CodingKey {
        case time
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        // 经过 userInfo 读取版别信息
        if let versionC = encoder.userInfo[VersionController.infoKey] as? VersionController {
            let dateString = versionC.formatter.string(from: time)
            try container.encode(dateString, forKey: .time)
        } else {
            fatalError()
        }
    }
}
let s = SomeThing(time: Date())
let verC1 = VersionController(apiVersion: .v1)
try! encode(of: s, optional: verC1)
//{
//    "time" : "Nov-14-2017 20:01:55 GMT+8"
//}
let verC2 = VersionController(apiVersion: .v2)
try! encode(of: s, optional: verC2)
//{
//    "time" : "2017-11-14 20:03:47 +0800"
//}

处理 key 不确定的模型。

有一种很特其他情况便是咱们得到这样一个 json 数据:

let res = """
{
    "1" : {
        "name" : "ZhangSan"
    },
    "2" : {
        "name" : "LiSi"
    },
    "3" : {
        "name" : "WangWu"
    }
}
"""

这时 struct 类型的 Codingkeys 相当于一个 key 为 string,且 key 名不固定的 CodingKey

struct Student: Codable {
    let id: Int
    let name: String
}
struct StudentList: Codable {
    var students: [Student] = []
    init(students: Student ... ) {
        self.students = students
    }
    struct Codingkeys: CodingKey {
        var intValue: Int? { return nil }
        init?(intValue: Int) { return nil }
        var stringValue: String //json 中的 key
        // 根据 key 来创立 Codingkeys,来读取 key 中的值
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        // 相当于 enum 中的 case
        // 其实便是读取 key 是 name 所应对的值
        static let name = Codingkeys(stringValue: "name")!
    }
    init(from decoder: Decoder) throws {
        // 指定映射规矩
        let container = try decoder.container(keyedBy: Codingkeys.self)
        var students: [Student] = []
        for key in container.allKeys { //key 的类型便是映射规矩的类型 (Codingkeys)
            if let id = Int(key.stringValue) { // 首要读取 key 自身的内容
                // 创立内嵌的 keyedContainer 读取 key 对应的字典,映射规矩同样是 Codingkeys
                let keyedContainer = try container.nestedContainer(keyedBy: Codingkeys.self, forKey: key)
                let name = try keyedContainer.decode(String.self, forKey: .name)
                let stu = Student(id: id, name: name)
                students.append(stu)
            }
        }
        self.students = students
    }
    func encode(to encoder: Encoder) throws {
        // 指定映射规矩
        var container = encoder.container(keyedBy: Codingkeys.self)
        try students.forEach { stu in
            // 用 Student 的 id 作为 key,然后该 key 对应的值是一个字典,所以咱们创立一个处理字典的子容器
            var keyedContainer = container.nestedContainer(keyedBy: Codingkeys.self, forKey: Codingkeys(stringValue: "\(stu.id)")!)
            try keyedContainer.encode(stu.name, forKey: .name)
        }
    }
}
let stuList2 = try! decode(of: res, type: StudentList.self)
dump(stuList2)
//▿ __lldb_expr_752.StudentList
//  ▿ students: 3 elements
//    ▿ __lldb_expr_752.Student
//      - id: 2
//      - name: "LiSi"
//    ▿ __lldb_expr_752.Student
//      - id: 1
//      - name: "ZhangSan"
//    ▿ __lldb_expr_752.Student
//      - id: 3
//      - name: "WangWu"
let stu1 = Student(id: 1, name: "ZhangSan")
let stu2 = Student(id: 2, name: "LiSi")
let stu3 = Student(id: 3, name: "WangWu")
let stuList1 = StudentList(students: stu1, stu2, stu3)
try! encode(of: stuList1)
//{
//    "1" : {
//        "name" : "ZhangSan"
//    },
//    "2" : {
//        "name" : "LiSi"
//    },
//    "3" : {
//        "name" : "WangWu"
//    }
//}

过错信息:能够经过规范库的编解码 error 获取过错信息。

public enum DecodingError : Error {
    // 在呈现过错时经过 context 来获取过错的详细信息
    public struct Context {
        public let codingPath: [CodingKey]
        // 过错信息中的详细过错描述
        public let debugDescription: String
        public let underlyingError: Error?
        public init(codingPath: [CodingKey], debugDescription: String, underlyingError: Error? = default)
    }
    /// 下面是过错的类型
    // JSON 值和 model 类型不匹配
    case typeMismatch(Any.Type, DecodingError.Context)
    // 不存在的值
    case valueNotFound(Any.Type, DecodingError.Context)
    // 不存在的 key
    case keyNotFound(CodingKey, DecodingError.Context)
    // 不合法的 JSON 格局
    case dataCorrupted(DecodingError.Context)
}
public enum EncodingError : Error {
    // 在呈现过错时经过 context 来获取过错的详细信息
    public struct Context {
        public let codingPath: [CodingKey]
        // 过错信息中的详细过错描述
        public let debugDescription: String
        public let underlyingError: Error?
        public init(codingPath: [CodingKey], debugDescription: String, underlyingError: Error? = default)
    }
    // 特点的值与类型不合符
    case invalidValue(Any, EncodingError.Context)
}

那么,有没有更灵敏易用的计划呢?咱们先来着眼看看一些老练的 Objective-C 计划吧。

由于业务中更常运用 JSON 作为与后端的数据交换,所以下面的计划进针对 JSON 进行序列化/反序列化。

Objective-C JSON <-> Model 计划

这儿仅介绍 YYModel 和 JSONModel 的特性和运用,其他计划都大同小异。这儿列出些常用的计划介绍:

  • Mantle/Mantle: Model framework for Cocoa and Cocoa Touch

    • Mantle 源码阅览 – 飞书云文档
  • BDModel
    • 西瓜 iOS 序列化计划统一 BDModel – 飞书云文档
    • 西瓜 iOS Model 规划规范&最佳实践 – 飞书云文档

YYModel

Objective-C 完结。高功用 iOS/OSX 模型转化结构。运用 Objective-C 的 Runtime 对类方针特点进行主动赋值,并供给许多实用的扩展办法。

耗时比照:

Manually<YYModel<MJExtension<JSONModel<MantleManually < YYModel < MJExtension < JSONModel < Mantle

运用场景

  • Model: NSObject <-> Dictionary/Array
  • Model: NSObject/[Model] <-> JSON
  • 完结了 NSCodingNSCopying-hash-isEqual:
  • 运用字典装备特点。

优势:

  • 运用 Runtime 动态根据头文件界说的特点完结序列化、反序列化的进程。
  • 高功用:模型转化功用接近手写解析代码。
  • 主动类型转化:方针类型能够主动转化,详情见下方表格。
  • 类型安全:转化进程中,一切的数据类型都会被检测一遍,以保证类型安全,防止溃散问题。
  • 无侵入性:模型无需承继自其他基类。

当 JSON/Dictionary 中的方针类型与 Model 特点不共同时,YYModel 将会进行如下主动转化。主动转化不支撑的值将会被疏忽,以防止各种潜在的溃散问题。

JSON/Dictionary Model
NSString NSNumber,NSURL,SEL,Class
NSNumber NSString
NSString/NSNumber 根底类型 (BOOL,int,float,NSUInteger,UInt64,…) NaN 和 Inf 会被疏忽
NSString NSDate 以下列格局解析:yyyy-MM-dd yyyy-MM-dd HH:mm:ss yyyy-MM-dd’T’HH:mm:ss yyyy-MM-dd’T’HH:mm:ssZ EEE MMM dd HH:mm:ss Z yyyy
NSDate NSString 格局化为 ISO8601: “YYYY-MM-dd’T’HH:mm:ssZ”
NSValue struct (CGRect,CGSize,…)
NSNull nil,0
“no”,”false”,… @(NO),0
“yes”,”true”,… @(YES),1

自界说才能:

  • 自界说特点名到 JSON key 的映射。
  • 自界说容器元素类型。
  • 自界说特点处理黑名单和白名单。
  • 自界说数据校验。
  • 自界说数据转化。

约束:

Objective-C only。或者在 Swift 中声明一个 Objective-C 的类:

  • 承继于 NSObject;
  • 特点需为变量;
  • 特点应有 @objc 润饰。

实践

ibireme/YYModel: High performance model framework for iOS/OSX.

主动完结 NSObject、NSCoding、NSCopying:

@interface YYShadow :NSObject <NSCoding, NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGSize size;
@end
@implementation YYShadow
// 直接增加以下代码即可主动完结
- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; }
- (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; return [self yy_modelInitWithCoder:aDecoder]; }
- (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; }
- (NSUInteger)hash { return [self yy_modelHash]; }
- (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; }
- (NSString *)description { return [self yy_modelDescription]; }
@end

Model <-> Dictionary/JSON

// 将 JSON (NSData,NSString,NSDictionary) 转化为 Model:
User *user = [User yy_modelWithJSON:json];
// 将 Model 转化为 JSON 方针:
NSDictionary *json = [user yy_modelToJSONObject];

自界说特点名与 key 映射:

//回来一个 Dict,将 Model 特点名对映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"name" : @"n",
             @"page" : @"p",
             @"desc" : @"ext.desc",
             @"bookID" : @[@"id",@"ID",@"book_id"]};
}

容器类特点:

@class Shadow, Border, Attachment;
@interface Attributes
@property NSString *name;
@property NSArray *shadows; //Array<Shadow>
@property NSSet *borders; //Set<Border>
@property NSMutableDictionary *attachments; //Dict<NSString,Attachment>
@end
@implementation Attributes
// 回来容器类中的所需求寄存的数据类型 (以 Class 或 Class Name 的方式)。
+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"shadows" : [Shadow class],
             @"borders" : Border.class,
             @"attachments" : @"Attachment" };
}
@end

黑名单与白名单:

@interface User
@property NSString *name;
@property NSUInteger age;
@end
@implementation Attributes
// 假如完结了该办法,则处理进程中会疏忽该列表内的一切特点
+ (NSArray *)modelPropertyBlacklist {
    return @[@"test1", @"test2"];
}
// 假如完结了该办法,则处理进程中不会处理该列表外的特点。
+ (NSArray *)modelPropertyWhitelist {
    return @[@"name"];
}
@end

数据校验与自界说转化:

// JSON:
{
  "name":"Harry",
  "timestamp" : 1445534567
}
// Model:
@interface User
@property NSString *name;
@property NSDate *createdAt;
@end
@implementation User
// 当 JSON 转为 Model 完结后,该办法会被调用。
// 你能够在这儿对数据进行校验,假如校验不经过,能够回来 NO,则该 Model 会被疏忽。
// 你也能够在这儿做一些主动转化不能完结的作业。
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
    NSNumber *timestamp = dic[@"timestamp"];
    if (![timestamp isKindOfClass:[NSNumber class]]) return NO;
    _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
    return YES;
}
// 当 Model 转为 JSON 完结后,该办法会被调用。
// 你能够在这儿对数据进行校验,假如校验不经过,能够回来 NO,则该 Model 会被疏忽。
// 你也能够在这儿做一些主动转化不能完结的作业。
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
    if (!_createdAt) return NO;
    dic[@"timestamp"] = @(n.timeIntervalSince1970);
    return YES;
}
@end

JSONModel

运用场景

  • Model: NSObject <-> Dictionary/Array
  • Model: NSObject/[Model] <-> JSON

自界说:

  • 自界说全局特点名与 key 映射。
  • 自界说特点名与 key 的映射,支撑跨层级的映射。
  • 主动驼峰命名转化。
  • 可选类型特点。
  • 疏忽特点/黑名单。
  • 自界说数据转化。
  • 自界说 getters/setters。
  • 自界说 JSON 校验。
  • 自带 HTTP 恳求接口。

约束:

  • 模型需求承继自 JSONModel。
  • 同样也需求 Objective-C 风格的 Model 界说。

实践

根本运用:

// 类型界说
@interface CountryModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *country;
@property (nonatomic) NSString *dialCode;
@property (nonatomic) BOOL isInEurope;
@end
// JSON -> Mdoel,需求运用初始化办法。
NSError *error;
CountryModel *country = [[CountryModel alloc] initWithString:myJson error:&error];
ProductModel *pm = [ProductModel new];
pm.name = @"Some Name";
// Model -> Dictionary
NSDictionary *dict = [pm toDictionary];
// Model -> JSON string
NSString *string = [pm toJSONString];

自界说 key mapping,支撑跨层级:

{
  "orderId": 104,
  "orderDetails": {
    "name": "Product #1",
    "price": {
      "usd": 12.95
    }
  }
}
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *productName;
@property (nonatomic) float price;
@end
@implementation OrderModel
+ (JSONKeyMapper *)keyMapper
{
  return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
    @"id": @"orderId",
    @"productName": @"orderDetails.name",
    @"price": @"orderDetails.price.usd"
  }];
}
@end

主动驼峰 key mapping:

{
  "order_id": 104,
  "order_product": "Product #1",
  "order_price": 12.95
}
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) NSString *orderProduct;
@property (nonatomic) float orderPrice;
@end
@implementation OrderModel
+ (JSONKeyMapper *)keyMapper
{
  return [JSONKeyMapper mapperForSnakeCase];
}
@end

可选类型:

{
  "id": 123,
  "name": null,
  "price": 12.95
}
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Optional> *name;
@property (nonatomic) float price;
@property (nonatomic) NSNumber <Optional> *uuid;
@end

疏忽特点:

{
  "id": 123,
  "name": null
}
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Ignore> *customProperty;
@end

在办法内动态设置可选:

{
  "id": null
}
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@end
@implementation ProductModel
+ (BOOL)propertyIsOptional:(NSString *)propertyName
{
  if ([propertyName isEqualToString:@"id"])
    return YES;
  return NO;
}
@end

自界说转化:

@interface JSONValueTransformer (CustomTransformer)
@end
@implementation JSONValueTransformer (CustomTransformer)
- (NSDate *)NSDateFromNSString:(NSString *)string
{
  NSDateFormatter *formatter = [NSDateFormatter new];
  formatter.dateFormat = APIDateFormat;
  return [formatter dateFromString:string];
}
- (NSString *)JSONObjectFromNSDate:(NSDate *)date
{
  NSDateFormatter *formatter = [NSDateFormatter new];
  formatter.dateFormat = APIDateFormat;
  return [formatter stringFromDate:date];
}
@end

自界说 getters/setters:

@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
@end
@implementation ProductModel
- (void)setLocaleWithNSString:(NSString *)string
{
  self.locale = [NSLocale localeWithLocaleIdentifier:string];
}
- (void)setLocaleWithNSDictionary:(NSDictionary *)dictionary
{
  self.locale = [NSLocale localeWithLocaleIdentifier:dictionary[@"identifier"]];
}
- (NSString *)JSONObjectForLocale
{
  return self.locale.localeIdentifier;
}
@end

自界说 JSON 校验:

@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
@property (nonatomic) NSNumber <Ignore> *minNameLength;
@end
@implementation ProductModel
- (BOOL)validate:(NSError **)error
{
  if (![super validate:error])
    return NO;
  if (self.name.length < self.minNameLength.integerValue)
  {
    *error = [NSError errorWithDomain:@"me.mycompany.com" code:1 userInfo:nil];
    return NO;
  }
  return YES;
}
@end

Swift JSON <-> Model 计划

SwiftyJSON

Swift only,简化 JSON 处理,替代 JSONSerialization 的处理方式。让 JSON 直接转化为相似 Dictionary 的下标获取。经过 Optional 表达每个值。

所以,比照上面的 Objective-C 的几个计划,SwiftyJSON 就显得太弱了,只能说仅仅做到了更简略地读写 JSON 罢了。

运用场景

直接读写 JSON 的值。

优势:

  • 更简练、扁平化拜访 JSON。
  • 供给更安全的类型支撑。

约束:跟转 model 没有直接联系。

实践

根本运用:

let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string {
  //Now you got your value
}
// Getting an array of string from a JSON Array
let arrayNames =  json["users"].arrayValue.map {$0["name"].stringValue}
// Getting a string using a path to the element
let path: [JSONSubscriptType] = [1,"list",2,"name"]
let name = json[path].string
// Just the same
let name = json[1]["list"][2]["name"].string
// Alternatively
let name = json[1,"list",2,"name"].string

ObjectMapper

Swift only,JSON <-> Model。

运用场景

JSON <-> Mode

优势:

  • 支撑一切 Swift 类型。
    • Int
    • Bool
    • Double
    • Float
    • String
    • RawRepresentable (Enums)
    • Array<Any>
    • Dictionary<String, Any>
    • Object<T: Mappable>
    • Array<T: Mappable>
    • Array<Array<T: Mappable>>
    • Set<T: Mappable>
    • Dictionary<String, T: Mappable>
    • Dictionary<String, Array<T: Mappable>>
    • Optionals of all the above
    • Implicitly Unwrapped Optionals of the above
  • 支撑常量特点。
  • 支撑承继。
  • 支撑范型。
  • 易用,其自界说进程比运用 Codable 简略和灵敏很多。

自界说:

  • 自界说特点映射。
  • 兼容特点的自界说值的装备。由于在 mapping 办法中,只会成功取出值后才会赋值,所以直接在特点上去界说的默许值仍有效。

约束/要求:

  • 需求遵从 Mappable 协议。
  • 关于变量特点运用 Mappable 协议;关于常量特点运用 ImmutableMappable 协议。
  • 变量特点运用 <- 运算符做映射;常量特点运用 >>> 做映射。
  • 枚举需求遵从 RawRepresentable

实践

根本运用,运用 <- 运算符建立 JSON 到特点变量的赋值。

// class Model 界说
class User: Mappable {
    var username: String?
    var age: Int?
    var weight: Double!
    var array: [Any]?
    var dictionary: [String : Any] = [:]
    var bestFriend: User?                       // Nested User object
    var friends: [User]?                        // Array of Users
    var birthday: Date?
    required init?(map: Map) {
    }
    // Mappable
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
        weight      <- map["weight"]
        array       <- map["arr"]
        dictionary  <- map["dict"]
        bestFriend  <- map["best_friend"]
        friends     <- map["friends"]
        birthday    <- (map["birthday"], DateTransform())
    }
}
// struct Model 界说
struct Temperature: Mappable {
    var celsius: Double?
    var fahrenheit: Double?
    init?(map: Map) {
    }
    mutating func mapping(map: Map) {
        celsius   <- map["celsius"]
        fahrenheit  <- map["fahrenheit"]
    }
}
// 外部调用
// 运用 Model 的办法做转化
let user = User(JSONString: JSONString)
let JSONString = user.toJSONString(prettyPrint: true)
// 运用 Mapper 办法做转化
let user = Mapper<User>().map(JSONString: JSONString)
let JSONString = Mapper().toJSONString(user, prettyPrint: true)

支撑常量特点:

遵从 ImmutableMappable 协议,

class User: ImmutableMappable {
  let id: Int
  let name: String?
  init(map: Map) throws {
    id   = try map.value("id")
    name = try? map.value("name")
  }
  func mapping(map: Map) {
    id   >>> map["id"]
    name >>> map["name"]
  }
}
// 运用
try User(JSONString: JSONString)

跨层级拜访、key mapping 直接在 mapping 办法中进行:

"distance" : {
     "text" : "102 ft",
     "value" : 31
}
func mapping(map: Map) {
    distance <- map["distance.value"]
}
// 同样也支撑数组
distance <- map["distances.0.value"]
// 关于带有 . 的 key 也有对应的处理办法。
// 疏忽 key 中的 .
func mapping(map: Map) {
    identifier <- map["app.identifier", nested: false]
}
// 换成其他 nested key delimiter
func mapping(map: Map) {
    appName <- map["com.myapp.info->com.myapp.name", delimiter: "->"]
}

自界说转化:

birthday <- (map["birthday"], DateTransform())
// 界说自界说转化类型
public protocol TransformType {
    associatedtype Object
    associatedtype JSON
    func transformFromJSON(_ value: Any?) -> Object?
    func transformToJSON(_ value: Object?) -> JSON?
}
// 直接创立个转化变量
let transform = TransformOf<Int, String>(fromJSON: { (value: String?) -> Int? in
    // transform value from String? to Int?
    return Int(value!)
}, toJSON: { (value: Int?) -> String? in
    // transform value from Int? to String?
    if let value = value {
        return String(value)
    }
    return nil
})
id <- (map["id"], transform)
// 或直接一行写完
id <- (map["id"], TransformOf<Int, String>(fromJSON: { Int($0!) }, toJSON: { $0.map { String($0) } }))

总结

综上所述,在 Swift 中的序列化与反序列化计划选择能够参考:

  • 考虑依靠最少,原生支撑:Codable。
  • 仅仅简略存取 JSON,不需求太多自界说特性:Codable。
  • 灵敏自界说:ObjectMapper。
  • NSObject 子类:NSCoding、YYModel。

当然还要考虑项目中是否有现有的库依靠,如没有依靠 ObjectMapper,就优先考虑先运用其他的序列化计划了。