Swift Codable协议的使用

398 阅读5分钟

1 什么是Codable协议,解决了什么问题

Codable 是 Swift 4.0 推出的,可以将 JSON 数据自动解析为model的一个协议。旨在取代 NSCoding 协议,支持结构体、枚举和类, typealias Codable = Decodable & Encodable

2 怎么使用Codable协议

参见: Swift4中Codable的使用(一):www.jianshu.com/p/5dab5664a… 这篇文章还介绍了一些特殊情况的使用,比如

  • 数组作为JSON根对象
  • 纯数组中的对象带有唯一Key

具体可查看文章中的Demo

struct Student: Codable {
   let name: String
   let age: Int
}

当我们像上面这样定义Student时,编译器会自动生成实现Codable协议的方法

public protocol Encodable {
   public func encode(to encoder: Encoder) throws
}
public protocol Decodable {
   public init(from decoder: Decoder) throws
}
public typealias Codable = Decodable & Encodable

实际上,我们也可以直接实现Codable协议,也就是自己实现:

 public protocol Encodable {
   public func encode(to encoder: Encoder) throws
}
public protocol Decodable {
   public init(from decoder: Decoder) throws
}

这篇文章:Swift4中Codable的使用(二)www.jianshu.com/p/6db40c4c0… 有详细的讲解和Demo

对这篇文章的内容熟悉之后,才有可能看懂后面的内容

对于 处理带有派生关系的模型 或 处理key个数不确定的json 这篇文章 Swift4中Codable的使用(三)有详细介绍

3 直接使用Codable面临的问题

通过2,我们了解了Codable的基础使用,但是我们直接使用Codable的话,有以下问题:

如果接口返回的数据少了一个key,就会全部解析失败; 或者把属性定义为可选的,如果定义为可选的,使用时都要先解包,麻烦; 没有值的时候怎么设置默认值

4 直接使用Codable面临的问题的解决方案:

对于上面的问题 这篇文章: 使用 Property Wrapper 为 Codable 解码设定默认值 : onevcat.com/2020/11/cod… (解决方案1) 用属性包装 Property Wrapper 算是解决了一些痛点,但是没有全部解决默认值的问题,这个方法里面的默认值只是对数据类型的默认值, 使用者不能自定义默认值

2021 年了,Swift 的 JSON-Model 转换还能有什么新花样: zhuanlan.zhihu.com/p/351928579… (解决方案2)

解决方案2 实现了的功能相对多一些,但是代价就是自己实现了解码,而且属性包装器是一个Class,如果在Struct中使用这个属性包装器会有点问题,Struct是值类型,Class是引用类型

还有一个问题是定义属性的时候无法定义为 Any, Any不支持Codable

Swift是时候使用Codable了: juejin.cn/post/716874… 这篇文章对 方案2 进行了优化,实现了可以把属性定义为Any

讨论: 为什么方案1 不能定义默认值,方案2 可以呢?

下面的代码对方案1的完善


public protocol DecodableDefaultSource {
    associatedtype  Value: Decodable
    static var defaultValue: Value { get }
}

public enum DecodableDefault {}

public extension DecodableDefault {
    @propertyWrapper
    struct  Wrapper<Source: DecodableDefaultSource> {
        public init() {
        }
        
        public typealias  Value = Source.Value
        public var wrappedValue = Source.defaultValue
    }
}

extension DecodableDefault.Wrapper: Decodable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode(Value.self)
    }
}

public extension KeyedDecodingContainer {
    func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type,
                   forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
       return try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

public extension DecodableDefault {
    typealias Source = DecodableDefaultSource
    typealias List = Decodable & ExpressibleByArrayLiteral
    typealias Map = Decodable & ExpressibleByDictionaryLiteral
    
     enum Sources {
         public enum True: Source { public static var defaultValue: Bool { true } }
         public enum False: Source { public static var defaultValue: Bool { false } }
         public enum EmptyString: Source { public static var defaultValue: String { "" } }
         public enum EmptyList<T: List>: Source { public static var defaultValue: T { [] } }
         public enum EmptyMap<T: Map>: Source { public static var defaultValue: T { [:] } }
         public enum Zero: Source { public static var defaultValue: Int { 0 } }
    }
}

public extension DecodableDefault {
    typealias True = Wrapper<Sources.True>
    typealias False = Wrapper<Sources.False>
    typealias EmptyString = Wrapper<Sources.EmptyString>
    typealias EmptyList<T: List> = Wrapper<Sources.EmptyList<T>>
    typealias EmptyMap<T: Map> = Wrapper<Sources.EmptyMap<T>>
    typealias Zero = Wrapper<Sources.Zero>
}

extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}

extension DecodableDefault.Wrapper: Encodable where Value: Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
}

要使用方案1, 只需这样定义一个model即可

 struct ArticleModel: Codable {
    @DecodableDefault.Zero var title
    @DecodableDefault.EmptyString var created_at_str
}

使用

      let jsonString1123 = """
        {
        "title" : 22,
         "created_at_str111" : "2--",

        }
        """

        let jsonData1123 = jsonString1123.data(using: .utf8)
        let decoder1123 = JSONDecoder()
        if let jsonData1123 = jsonData1123,
           let result = try? decoder1123.decode(ArticleModel.self, from: jsonData1123) {
            print(result)
        } else {
            print("解析失败")
        }

我们来分析一下解析的流程

ArticleModel 中定义的title 和created_at_str本质是一个struct,所以 解析的时候会调用Wrapper的解析方法,也就是会调用到

##1
extension DecodableDefault.Wrapper: Decodable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode(Value.self)
    }
}

但是调用上面这个方法的前提是 json数据中,有modle的key; 如果key不存在,就不会调用上面的方法,而是直接报错,整个modle都无法解析,为了解决这个问题我们重写了下面这个方法

##2 
public extension KeyedDecodingContainer {
   func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type,
                  forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
      return try decodeIfPresent(type, forKey: key) ?? .init()
   }
}

也就是如果key 不存在,我们就返回 DecodableDefault.Wrapper.init(),在init的时候没有办法获取定义时的默认值,所以无法实现随意定义默认值

注意:调用的时候是先调用 ##2 ,在 ##2 中的 try decodeIfPresent(type, forKey: key) 方法中,如果key存在, 会触发调用##1;注意不是执行完decodeIfPresent(type, forKey: key),再调用##1,而是在decodeIfPresent(type, forKey: key)执行的过程中会调用##1。 如果key不存在就不会调用##1了,decodeIfPresent(type, forKey: key)也会返回nil,

要使用方案2, 只需这样定义一个model即可

struct TestStruct: Equatable, ExAutoCodable {
   @ExCodable("shantt") // 字段和属性同名可以省掉字段名和括号,但 `@ExCodable` 还是没办法省掉
   var int: Int = 29
   @ExCodable("string", "str", "s", "nested.string") // 支持多个 key 以及嵌套 key 可以这样写
   var string: String? = "sadfj"
}

注意 我们在使用方案1的时候是这么定义的 struct ArticleModel: Codable ,ArticleModel只遵守了系统的Codable协议,而使用 方案2 的时候 struct TestStruct: Equatable, ExAutoCodable , 遵守了ExAutoCodable协议,这协议就是自定义编解码的。

个人认为一般的业务使用 方案1 就够了,

相关文档

  1. 关于Codable协议处理数据实体属性缺省值问题 : juejin.cn/post/695882…
  2. 有关Swift Codable解析成Dictionary<String, Any>的一些事 : juejin.cn/post/702841…
  3. 使用 Property Wrapper 为 Codable 解码设定默认值 : onevcat.com/2020/11/cod…
  4. Codable保姆级攻略 : juejin.cn/post/712266…
  5. Swift是时候使用Codable了 : juejin.cn/post/716874…
  6. Swift协议Codable底层探索及应用 : juejin.cn/post/693838…
  7. 利用Swift的反射将Struct转换成Dictionary :hisoka0917.github.io/swift/2018/…
  8. Swift - Codable 使用小记 : yupeng.fun/2021/06/08/…
  9. 【译】Swift&JSON 从入门到精通 :juejin.cn/post/704487…
  10. Encoding and Decoding in Swift : www.kodeco.com/3418439-enc…
  11. Swift4中Codable的使用(一):www.jianshu.com/p/5dab5664a…
  12. Swift4中Codable的使用(二):www.jianshu.com/p/6db40c4c0…
  13. Swift4中Codable的使用(三):www.jianshu.com/p/bf56a7432…
  14. 利用 Decoder 來解析 JSON 要懂的那些方法 : franksios.medium.com/%E5%88%A9%E…
  15. iOS Swift 原生 字典数组转模型 JSONDecoder 对象存储 NSKeyedArchiver : www.jianshu.com/p/0fa4e0ee0…
  16. Swift 中的 JSON 反序列化: zhuanlan.zhihu.com/p/541854557