Codable、编码器、解码器

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=汪汪)
发布了40 篇原创文章 · 获赞 10 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/ai_pple/article/details/103734588