Codable是序列化与反序列化的一个协议,这里贴出来如何实现Codable协议及达到使用系统编码解码器(如JSONEncoder和JSONDecoder),另外再自己自定义一个编码器以便更好理解编码解码的工作原理
使用Codable
//MARK: 使用Codable
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
// 标准库的所有基本类型(bool、String等),都实现了Codable
// 由于所有的存储属性都是可以编解码的,所以Swift编码器会自动生成实现协议的代码
}
struct Placemark: Codable {
var name: String
var coordinate: Coordinate
// 同理会自动生成实现代码
// 也可以自实现(不想自实现从这里到init方法全删掉就行)
private enum CodingKeys: String,CodingKey {
case name
case coordinate = "coor"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(coordinate, forKey: .coordinate)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.coordinate = try container.decode(Coordinate.self, forKey: .coordinate)
}
init(name: String, coordinate: Coordinate) {
self.name = name
self.coordinate = coordinate
}
}
// 使用
func JSONcode() {
//实例数组
let places = [Placemark(name: "a", coordinate: Coordinate(latitude: 1, longitude: 1)),
Placemark(name: "b", coordinate: Coordinate(latitude: 2, longitude: 2))]
// 编码
let encoder = JSONEncoder()
var jsonData : Data?
do {
jsonData = try encoder.encode(places) // 该方法接受一个实现了Encodable协议的实例,返回Data类型
print("编码结果"+String(decoding: jsonData!, as: UTF8.self))
} catch {
print(error)
}
// 解码
let decoder = JSONDecoder()
do {
let dePlaces = try decoder.decode([Placemark].self, from:jsonData!)
print("解码结果:\(dePlaces)");
} catch {
print(error)
}
}
我们看Swift的源码会发现,当我们调用JSONDecoder实例的decode方法时候,内部会创建一个__JSONEncoder实例,由这个实例去调用_encoder.box_()方法,这个方法内部再调用我们实现的协议方法encode(),如下伪代码
/* JSONEncoder()实例,调用encoder.encode(places) {
__JSONEncoder实例,调用_encoder.box_(places) {
调用 place.encode(to self)
}
}
*/
当我们在协议实现里调用encoder.container(keyedBy: CodingKeys.self)的时候,会返回一个叫做键值容器,这个容器实现KeyedEncodingContainerProtocol协议,定义了编码存储键值队类型,编码后的结果就保存在字典里
其他的还有无键容器,它实现UnkeyedEncodingContainer协议,定义编码存储一系列值,编码的结果保存在数组里
单一容器,实现SingleValueEncodingContainer协议,定义编码存储单一值
仿照源码实现简单的编码器
这里仿照源码,实现一个简单的将实例编码成字符串形式的编码器,由于简单,很多方法没有用到,感兴趣的同学可以自行补充完整,或留言告诉我想要实现什么样的效果,这里只把用到的代码贴出,想要看运行效果,自行去github下载
创建一个编码器
class DIYEncoder: Encoder {
var userInfo: [CodingUserInfoKey : Any] = [:]
fileprivate var storage: DIYEncodingStorage
public var codingPath: [CodingKey] = []
init(codingPath: [CodingKey] = []) {
self.codingPath = codingPath
self.storage = DIYEncodingStorage()
}
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
let topContainer: NSMutableString
if self.canEncodeNewValue {
topContainer = self.storage.pushKeyedContainer()
} else {
guard let container = self.storage.containers.last else {
preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.")
}
topContainer = container
}
let container = DIYKeyContainer<Key>(encoder: self, codingPath: self.codingPath, container: topContainer)
return KeyedEncodingContainer(container)
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
// 未验证。。随便写了些代码
}
func singleValueContainer() -> SingleValueEncodingContainer {
// 未验证。。随便写了些代码
}
fileprivate var canEncodeNewValue: Bool {
return self.storage.count == self.codingPath.count
}
func encode<T : Encodable>(_ value: T) throws -> String {
try value.encode(to: self)
return self.storage.popContainer() as String
}
}
创建键值容器,实现键值编码规则
// 键值容器
fileprivate struct DIYKeyContainer<K : CodingKey> : KeyedEncodingContainerProtocol {
private var container: NSMutableString
private let encoder: DIYEncoder
var codingPath: [CodingKey]
fileprivate init(encoder: DIYEncoder, codingPath:[CodingKey], container: NSMutableString) {
self.encoder = encoder
self.codingPath = codingPath
self.container = container
}
private func append(_ str: String) {
if container.length > 0 {
container.append(",")
}
container.append(str)
}
mutating func encodeNil(forKey key: K) throws {
}
mutating func encode(_ value: Bool, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: String, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: Double, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: Float, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: Int, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: Int8, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: Int16, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: Int32, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: Int64, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: UInt, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: UInt8, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: UInt16, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: UInt32, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
mutating func encode(_ value: UInt64, forKey key: K) throws {
self.append("\(key.stringValue)=\(value)")
}
// 如果值不是普通类型,需要对值再次编码,再次编码的话就新建一个字符串序列,用于存储这个值编码后的结果
mutating func encode<T>(_ value: T, forKey key: K) throws where T : Encodable {
var valueStr = ""
self.append("\(key.stringValue)=")
let depth = self.encoder.storage.count
do {
self.container = NSMutableString()
self.encoder.storage.push(container: self.container)
try value.encode(to: self.encoder)
} catch {
if self.encoder.storage.count > depth {
let _ = self.encoder.storage.popContainer()
}
throw error
}
if self.encoder.storage.count > depth {
valueStr = self.encoder.storage.popContainer() as String
}
self.container = NSMutableString.init(string: "\(self.encoder.storage.popContainer())(\(self.container))")
self.encoder.storage.push(container: self.container)
}
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: K) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
let containerKey = key
let dictionary: NSMutableString
dictionary = NSMutableString()
self.container.append(dictionary as String)
self.codingPath.append(key)
defer { self.codingPath.removeLast() }
let container = DIYKeyContainer<NestedKey>(encoder: self.encoder, codingPath: self.codingPath, container: dictionary)
return KeyedEncodingContainer(container)
}
mutating func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer {
let containerKey = key
let dictionary: NSMutableString
dictionary = NSMutableString()
self.container.append(dictionary as String)
self.codingPath.append(key)
defer { self.codingPath.removeLast() }
let container = DIYContainer(encoder: self.encoder, codingPath: self.codingPath, container: dictionary)
return container
}
mutating func superEncoder() -> Encoder {
return self.encoder
}
mutating func superEncoder(forKey key: K) -> Encoder {
return self.encoder
}
}
创建一个Storage,负责存储编码的值
fileprivate struct DIYEncodingStorage {
private(set) fileprivate var containers: [NSMutableString] = []
fileprivate init() {}
fileprivate var count: Int {
return self.containers.count
}
fileprivate mutating func pushKeyedContainer() -> NSMutableString {
let dictionary = NSMutableString()
self.containers.append(dictionary)
return dictionary
}
fileprivate mutating func pushUnkeyedContainer() -> NSMutableString {
let array = NSMutableString()
self.containers.append(array)
return array
}
// 添加一个字符串序列
fileprivate mutating func push(container: __owned NSMutableString) {
self.containers.append(container)
}
// 拿出来一个字符串序列
fileprivate mutating func popContainer() -> NSMutableString {
return self.containers.popLast() ?? ""
}
}
运行测试
struct Pet: Codable {
var name : String
var call : String
}
//MARK: 使用Codable
struct Person: Codable {
var age: Int
var name: String
var height: Double
var pet: Pet
}
let pet = Pet(name: "dog", call: "汪汪")
let per = Person(age: 2, name: "ai_pple", height: 158.6, pet: pet)
let encoder = DIYEncoder()
var str : String?
do {
str = try encoder.encode(per)
print("\(str ?? "")")
} catch {
print(error)
}
输出结果:
age=2,name=ai_pple,height=158.6,pet=(name=dog,call=汪汪)