美文网首页
Swift 网络数据模型解析

Swift 网络数据模型解析

作者: harvey_dong | 来源:发表于2018-07-18 10:35 被阅读326次

    引言

    在项目中网络数据解析是最普通的功能了。最常见的就是JSON数据。在项目最开始我们使用了Argo 进行模型解析。Argo+Curry+Runes完成网络数据转Model。但随着苹果发布swift 4.0 引入了 JSONDecoder、JSONEncoder,加上Argo 也不再维护。所以我们准备重新设计数据模型解析。
    这里我就不介绍JSONDecoderCodable相关内容了。不了解的可以先自行百度。这次只分享在实现过程中遇到的坑及解决方法。
    网络请求是一个不稳定的过程,接口数据不可能和理想数据完全一样。你可能会遇到某一个字段找不到;本来是Int,结果是String(这点在php 后台非常明显);本来是String,结果来了个Int 。Swift 是一个类型严格的语言。只要类型不一样,就随时可能crash。所以要开发模型解析库,第一个要解决的问题就是类型安全问题。参考其他网络模型解析(出名的有OC 的YYModelMJExtension,swift有之前使用的Argo)。在数据解析过程中如果接口数据不是目标类型,那么会做一次类型转换。那么在Swift 中是否也可以这样做那。先来看下Swift 的类型转换方法吧!
    键值对的解析 KeyedDecodingContainer

    public func decode(_ type: Bool.Type, forKey key: KeyedDecodingContainer.Key) throws -> Bool
    public func decode(_ type: String.Type, forKey key: KeyedDecodingContainer.Key) throws -> String
    public func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T where T : Decodable
    

    这里可以看到解析时要先传入类型和相对应的key。我尝试过。这种解析只会给什么类型就解析什么。不能达到我们前面提到的目的。所以只有自己动手才能丰衣足食啊。


    流泪

    首先来分析下原生解析。对于绝大多数情况,一般我们定义什么类型就期望解析成什么类型。比如

    struct Person: Decodable {
        let name: String
        let age: Int
        
        enum PersonCodingKey: String, CodingKey {
            case name
            case age
        }
        
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: PersonCodingKey.self)
            name = try container.decode(String.self, forKey: .name)
            age = try container.decode(Int.self, forKey: .age)
        }
    }
    

    但这里String.self 参数其实可以去掉。因为Swift 是支持泛型的。理想情况是我告诉container 我要解析.name 。那么它应该能够推断出String 类型。
    那么我们最终的处理结果是

    1. 要对数据类型容错
    2. 避免重复类型的声明,让编译器自动推导

    最终我封装成了下面的代码

    // MARK: - 键值对解码
    extension KeyedDecodingContainer {
     func decode<T: Decodable>(key: KeyedDecodingContainer.Key) throws -> T {
            var value: T
            if T.self == Int.self {
                value = try _safeDecode(Int.self, forKey: key) as! T
            }else if T.self == Int8.self {
                value = try _safeDecode(Int8.self, forKey: key) as! T
            }else if T.self == Int16.self {
                value = try _safeDecode(Int16.self, forKey: key) as! T
            }else if T.self == Int32.self {
                value = try _safeDecode(Int32.self, forKey: key) as! T
            }else if T.self == Int64.self {
                value = try _safeDecode(Int64.self, forKey: key) as! T
            }else if T.self == UInt8.self {
                value = try _safeDecode(UInt8.self, forKey: key) as! T
            }else if T.self == UInt16.self {
                value = try _safeDecode(UInt16.self, forKey: key) as! T
            }else if T.self == UInt32.self {
                value = try _safeDecode(UInt32.self, forKey: key) as! T
            }else if T.self == UInt64.self {
                value = try _safeDecode(UInt64.self, forKey: key) as! T
            }else if T.self == String.self {
                value = _safeDecode(String.self, forKey: key) as! T
            }else if T.self == Bool.self {
                value = try _safeDecode(Bool.self, forKey: key) as! T
            }else if T.self == Float.self {
                value = try _safeDecode(Float.self, forKey: key) as! T
            }else if T.self == Double.self {
                value = try _safeDecode(Double.self, forKey: key) as! T
            }else {
                value = try decode(T.self, forKey: key)
            }
            return value
        }
      private func _safeDecode(_ type: Int.Type, forKey key: KeyedDecodingContainer.Key) throws -> Int  {
            var value: Int?
            do {
                value = try decode( Int.self, forKey: key)
            } catch {
                let err = error as! DecodingError
                switch err {
                case .typeMismatch(_, _):
                    value = try (decodeIfPresent(String.self, forKey: key) as NSString?)?.integerValue
                    if value == nil {
                        throw err
                    }
                default:
                    throw err
                }
            }
            return value!
        }
    }
    

    篇幅有限不全贴代码了。思路就是对所有基本类型做封装,如果直接转换失败,遇到了typeMismatch 错误。那么再次尝试其他类型转换。比如常见的String -> Int
    String->Float, String -> Double, Int > Bool ,String -> Bool等。
    所以现在的模型解析就可以简化成这样了。

     public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: PersonCodingKey.self)
            name = try container.decode(key: .name)
            age = try container.decode(key: .age)
        }
    

    这样解析语义也更加清晰。
    按照这个思路对其他进行扩展

    extension SingleValueDecodingContainer {
        func decode<T: Decodable>() throws -> T {
            var value: T
            if T.self == Int.self {
                value = try _safedecode(Int.self) as! T
            }else if T.self == String.self {
                value = try _safedecode(String.self) as! T
            } else if T.self == Float.self {
                value = try _safedecode(Float.self) as! T
            }else {
                value = try decode(T.self)
            }
            return value
        }
    }
    

    数组解析

    extension KeyedDecodingContainer {
     //MARK: collection解码
        /* 序列编码 目前只自定义了Array  */
        func sequenceDecode<T: Collection & Decodable>(key: KeyedDecodingContainer.Key) throws -> T where T.Element: Decodable {
            var values: T
            if T.self == Array<T.Element>.self {
                var array = Array<T.Element>.init()
                var unkeyed = try nestedUnkeyedContainer(forKey: key)
                while !unkeyed.isAtEnd {
                    var subValue: T.Element? = nil
                    if T.Element.self == Int.self {
                        subValue = decode(Int.self, unkeyed: &unkeyed) as? T.Element
                    }else if T.Element.self == String.self {
                        subValue = decode(String.self, unkeyed: &unkeyed) as? T.Element
                    }else if T.Element.self == Float.self {
                        subValue = decode(Float.self, unkeyed: &unkeyed) as? T.Element
                    }else {
                        subValue = try unkeyed.decode(T.Element.self)
                    }
                    if subValue != nil {
                        array.append(subValue!)
                    }
                }
                values = array as! T
            }else{
                values = try decode(T.self, forKey: key)
            }
            return values
        }
    }
    

    我只对基本类型进行了扩展。防止某一个item解析失败导致整个数组解析都失败。
    在实际开发中有些字段并不是must 的,所以一些optional 字段是可以解析失败的。于是又扩展了对 optional 字段解析

     /* 解码optional 的 不会转码失败 */
        func decodeIfPresent<T: Decodable>(key: KeyedDecodingContainer.Key) -> T? {
            var value: T??
            if T.self == Int.self {
                value = try? _safeDecode(Int.self, forKey: key) as? T
            }else if T.self == Int8.self {
                value = try? _safeDecode(Int8.self, forKey: key) as? T
            }else if T.self == Int16.self {
                value = try? _safeDecode(Int16.self, forKey: key) as? T
            }else if T.self == Int32.self {
                value = try? _safeDecode(Int32.self, forKey: key) as? T
            }else if T.self == Int64.self {
                value = try? _safeDecode(Int64.self, forKey: key) as? T
            }else if T.self == UInt8.self {
                value = try? _safeDecode(UInt8.self, forKey: key) as? T
            }else if T.self == UInt16.self {
                value = try? _safeDecode(UInt16.self, forKey: key) as? T
            }else if T.self == UInt32.self {
                value = try? _safeDecode(UInt32.self, forKey: key) as? T
            }else if T.self == UInt64.self {
                value = try? _safeDecode(UInt64.self, forKey: key) as? T
            }else if T.self == String.self {
                value = _safeDecode(String.self, forKey: key) as? T
            }else if T.self == Bool.self {
                value = try? _safeDecode(Bool.self, forKey: key) as? T
            }else if T.self == Float.self {
                value = try? _safeDecode(Float.self, forKey: key) as? T
            }else if T.self == Double.self {
                value = try? _safeDecode(Double.self, forKey: key) as? T
            }else {
                value = try? decode(T.self, forKey: key) as T?
            }
            if value == nil {
                return nil
            }
            return value!!
        }
    

    这样既可以保证数据的正确性又有很强的容错性。

    struct LifeStyleItem: JSONDecodable {
        let name: String //生活指数名字
        let icon: String
        let desc: String? //生活指数描述
        let suggest: String? //建议,目前服务器暂时不传
        let color: String //取值范围:"red", "green",red表示超标,green在安全值范围内
        let type: LifeStyleType  // "1"表示原生指数,"2"表示运营指数,等等(穿衣指数属于原生指数),目前服务器暂时不传,客户端保留,默认值为1
    }
    extension LifeStyleItem {
        private enum LifeStyleItemCodingKey: String, CodingKey {
            case name
            case icon
            case desc
            case suggest
            case color
            case type
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: LifeStyleItemCodingKey.self)
            name = try container.decode(key: .name)
            icon = try container.decode(key: .icon)
            desc = container.decodeIfPresent(key: .desc)
            suggest = container.decodeIfPresent(key: .suggest)
            color = try container.decode(key: .color)
            type = try container.decode(key: .type)
        }
    }
    

    整个解析差不多已经OK。现在还差最后一点。与网络接口对接。
    于是定义了以下协议:

    protocol JSONDecodable: Decodable {
        static func decode(_ json: [String: Any]?) -> Self?
        static func decode(_ json: [[String: Any]?]?) -> [Self]
    }
    extension JSONDecodable {
        static func decode(_ json: [String: Any]?) -> Self? {
            guard json != nil else {
                return nil
            }
            let data = try? JSONSerialization.data(withJSONObject: json!, options: JSONSerialization.WritingOptions(rawValue: 0))
            guard data != nil else {
                return nil
            }
            let decode = JSONDecoder.init()
            var model: Self? = nil
            do {
                model = try decode.decode(Self.self, from: data!)
            } catch {
                print("decode \(Self.self) error \(error)")
            }
            return model
        }
        static func decode(_ json: [[String: Any]?]?) -> [Self] {
            guard json != nil else {
                return []
            }
            var models: [Self] = []
            let decode = JSONDecoder.init()
            json!.forEach({ (jsonData: [String: Any]?) in
                if jsonData == nil {
                    return
                }
                let data = try? JSONSerialization.data(withJSONObject: jsonData!, options: JSONSerialization.WritingOptions(rawValue: 0))
                if data == nil {
                    return
                }
                var model: Self?
                do{
                    model = try decode.decode(Self.self, from: data!)
                }catch{
                    return
                }
                models.append(model!)
            })
            return models
        }
    }
    protocol JSONEncodable: Encodable {
        func encode() -> [String: Any]?
    }
    extension JSONEncodable {
        func encode() -> [String: Any]? {
            let encoder = JSONEncoder()
            let codedValue = try? encoder.encode(self)
            guard let codeData = codedValue else {
                return nil
            }
            let json = try? JSONSerialization.jsonObject(with: codeData, options: JSONSerialization.ReadingOptions(rawValue: 0))
            return json as? [String: Any]
        }
    }
    typealias JSONCodable = JSONDecodable & JSONEncodable
    

    这样模型就遵循 JSONCodable 并实现 DecodableEncodable 协议就可以完成模型解析。

    最后解析可以简化为

    let festival = Festival.decode(json)
    

    相关文章

      网友评论

          本文标题:Swift 网络数据模型解析

          本文链接:https://www.haomeiwen.com/subject/rrgnpftx.html