美文网首页IOS三人行iOS开发技巧分类
Swift4 终极解析方案:基础篇

Swift4 终极解析方案:基础篇

作者: Andy矢倉 | 来源:发表于2017-09-26 00:11 被阅读2184次

    转载请注明,原文地址:Swift4 终极解析方案:基础篇

    做过网络开发,特别是互联网,甚至移动端开发的,日常对于数据解析,早年主流的XML,现今主流的JSON都是非常熟悉的,说道解析,系统自带和各种第三方的解析库,除了解析当然也当不了懒癌的脚步,各种model反射库。
    对于Objective-C各种方案都尤为成熟,甚至还有专门的MacApp用于model生成,可以说是懒到极致,好处当然是节省出了撸猫撸手办的时间(技术狗撸手办不知道是什么时候开始的恶习,我还是更喜欢撸妹纸)。
    这种操作对于Swift就比较蛋疼了,当然第三方库和工具也是完全够用,但是,生成的model里面一大堆代码,一个字,恶心。

    那好,今年Swift更新到4.0版本之后带了一个我最喜欢的功能:Codable协议。

    CodableEncodableDecodable协议总和的别名。所以它既能编码也能解码,自从有了它,我model里面代码奏是干干净净,清清爽爽,对于洁癖控来说,这货是原生的,又可以少个PodPackage了,巴巴掌。

    开始吧

    如果对此协议不太明白到底能干啥,可以先看下今年的WWDC视频。

    Codable让我们可以通过StructClass不要一行多余代码来解析JSONPlist数据。基础库简直嗨的不要不要的。
    让我们来看一个例子:

    import Foundation
    
    struct Swifter: Decodable {
      let fullName: String
      let id: Int
      let twitter: URL
    }
    
    let json = """
    {
     "fullName": "Federico Zanetello",
     "id": 123456,
     "twitter": "http://twitter.com/zntfdr"
    }
    """.data(using: .utf8)! // our data in native (JSON) format
    
    let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // Decoding our data
    print(myStruct) // decoded!!!!!
    

    如果你看过Decodable文档,那你肯定知道里面有一个必须实现的方法init(from: Decoder)。然而示例里并没实现,照样跑得飞起来,那就是苹果爸爸妈宝当到家:编译器默认会帮我们实现一个。

    再看看另外一个例子:

    import Foundation
    
    enum BeerStyle : String, Codable {
        case ipa
        case stout
        case kolsch
        // ...
    }
    
    struct Beer {
        let name: String
        let brewery: String
        let style: BeerStyle
    }
    
    let json = """
    {
        "name": "Endeavor",
        "abv": 8.9,
        "brewery": "Saint Arnold",
        "style": "ipa"
    }
    """.data(using: .utf8)! // our data in native (JSON) format
    
    let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // Decoding our data
    print(myStruct) // decoded!!!!!
    

    看到这里是不是就被戳到G点了???如果后端没有啥特别的字段,你只需要把JSON里的Key作为Property即可。
    解析的条件就是,只要是系统提供的如StringnumberBool以及各类集合,只要是符合Decodable协议即可,简直是嗨到极点。

    自定义实现

    能够直接解析当然是最好的,但是往往开发的时候会遇到一些比较复杂的结构,那可能是ArrayDictionary相互嵌套,各个系统或者开发语言的保留字导致字段奇特,以及很多煞笔后端搞些魔术字啊,拼音命名的字段啥的。

    实际上开发大多遇到的都是这种情况,那就不得不出动自定义解析了。自定义的部分稍微复杂点,坐稳了别翻车。

    解码器

    Decoder负责处理JSONPlist解析工作,需要重点关注的两个方法:

    public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
    
    public func singleValueContainer() throws -> SingleValueDecodingContainer
    

    这两个方法返回的都是Container
    第一个方法返回keyedContainerKeyedDecodingContainer:如果我们要自定义解析,那就需要告诉解码器如何映射。
    第二个方法仅仅返回一个Container,而SingleValueDecodingContainer里的数据正是我们想要的。

    容器

    Decoder提供了基础的功能解析原始数据,自定义数据就需要我们自己来搞定。
    KeyedDecodingContainer:我们的容器是通过键值匹配的,所以大可以看作[Key: Any]这样的字典结构。
    不同的键对应不同的类型数据,所以容器提供的不同解码方法:decode(Type:forKey:)
    它的神奇之处就在于容器会自动匹配数据类型。
    当然,解码器也提供了通用方法:

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

    有了这个泛型解析方法,那就意味着任意类型都可以匹配解析。

    SingleValueDecodingContainer就更前面说的一样,只考虑返回。

    实现

    前面基础已经铺垫完了,现在就不要编译器帮忙了,我们自己手动来实现init(from: Decoder)

    第一步:选择正确的解码器

    示例里是JSON对象,那我们就选择JSONDecoder

    let decoder = JSONDecoder()
    

    JSONPlist解析器都是系统内置的,如果你想要,你也可以自己实现一个解析器解析你够奇葩的数据。

    第二步:选择正确的容器

    JSON数据如下:

    {
     "fullName": "Federico Zanetello",
     "id": 123456,
     "twitter": "http://twitter.com/zntfdr"
    }
    

    其实按照最开始说的,这个对象可以直接反射,辣么我们这里讲的自定义,那就按照自定义的套路走,我们按要求实现一个String结构体,并且满足CodingKey协议:

    enum MyStructKeys: String, CodingKey {
      case fullName = "fullName"
      case id = "id"
      case twitter = "twitter"
    }
    

    接下来创建容器:

    let container = try decoder.container(keyedBy: MyStructKeys.self)
    

    第三步:提取数据

    这里我们需要做类型转换:

    let fullName: String = try container.decode(String.self, forKey: .fullName)
    let id: Int = try container.decode(Int.self, forKey: .id)
    let twitter: URL = try container.decode(URL.self, forKey: .twitter)
    

    第四步:初始化

    使用默认的构造器:

    let myStruct = Swifter(fullName: fullName, id: id, twitter: twitter)
    

    现在我们就来看看全部实现:

    import Foundation
    
    struct Swifter {
      let fullName: String
      let id: Int
      let twitter: URL
      
      init(fullName: String, id: Int, twitter: URL) { // default struct initializer
        self.fullName = fullName
        self.id = id
        self.twitter = twitter
      }
    }
    
    extension Swifter: Decodable {
      enum MyStructKeys: String, CodingKey { // declaring our keys 
        case fullName = "fullName"
        case id = "id"
        case twitter = "twitter"
      }
      
      init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: MyStructKeys.self) // defining our (keyed) container
        let fullName: String = try container.decode(String.self, forKey: .fullName) // extracting the data
        let id: Int = try container.decode(Int.self, forKey: .id) // extracting the data
        let twitter: URL = try container.decode(URL.self, forKey: .twitter) // extracting the data
        
        self.init(fullName: fullName, id: id, twitter: twitter) // initializing our struct
      }
    }
    
    let json = """
    {
     "fullName": "Federico Zanetello",
     "id": 123456,
     "twitter": "http://twitter.com/zntfdr"
    }
    """.data(using: .utf8)! // our native (JSON) data
    
    let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // decoding our data
    print(myStruct) // decoded!
    

    复杂结构处理

    数组

    import Foundation
    
    struct Swifter: Decodable {
      let fullName: String
      let id: Int
      let twitter: URL
    }
    
    let json = """
    [{
     "fullName": "Federico Zanetello",
     "id": 123456,
     "twitter": "http://twitter.com/zntfdr"
    },{
     "fullName": "Federico Zanetello",
     "id": 123456,
     "twitter": "http://twitter.com/zntfdr"
    },{
     "fullName": "Federico Zanetello",
     "id": 123456,
     "twitter": "http://twitter.com/zntfdr"
    }]
    """.data(using: .utf8)! // our data in native format
    
    let myStructArray = try JSONDecoder().decode([Swifter].self, from: json)
    
    myStructArray.forEach { print($0) } // decoded!!!!!
    

    字典

    import Foundation
    
    struct Swifter: Codable {
      let fullName: String
      let id: Int
      let twitter: URL
    }
    
    let json = """
    {
      "one": {
        "fullName": "Federico Zanetello",
        "id": 123456,
        "twitter": "http://twitter.com/zntfdr"
      },
      "two": {
        "fullName": "Federico Zanetello",
        "id": 123456,
        "twitter": "http://twitter.com/zntfdr"
      },
      "three": {
        "fullName": "Federico Zanetello",
        "id": 123456,
        "twitter": "http://twitter.com/zntfdr"
      }
    }
    """.data(using: .utf8)! // our data in native format
    
    let myStructDictionary = try JSONDecoder().decode([String: Swifter].self, from: json)
    
    myStructDictionary.forEach { print("\($0.key): \($0.value)") } // decoded!!!!!
    

    枚举

    import Foundation
    
    struct Swifter: Decodable {
      let fullName: String
      let id: Int
      let twitter: URL
    }
    
    enum SwifterOrBool: Decodable {
      case swifter(Swifter)
      case bool(Bool)
    }
    
    extension SwifterOrBool: Decodable {
      enum CodingKeys: String, CodingKey {
        case swifter, bool
      }
      
      init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let swifter = try container.decodeIfPresent(Swifter.self, forKey: .swifter) {
          self = .swifter(swifter)
        } else {
          self = .bool(try container.decode(Bool.self, forKey: .bool))
        }
      }
    }
    
    let json = """
    [{
    "swifter": {
       "fullName": "Federico Zanetello",
       "id": 123456,
       "twitter": "http://twitter.com/zntfdr"
      }
    },
    { "bool": true },
    { "bool": false },
    {
    "swifter": {
       "fullName": "Federico Zanetello",
       "id": 123456,
       "twitter": "http://twitter.com/zntfdr"
      }
    }]
    """.data(using: .utf8)! // our native (JSON) data
    
    let myEnumArray = try JSONDecoder().decode([SwifterOrBool].self, from: json) // decoding our data
      
    myEnumArray.forEach { print($0) } // decoded!
    

    嵌套结构

    import Foundation
    
    struct Swifter: Decodable {
      let fullName: String
      let id: Int
      let twitter: URL
    }
    
    struct MoreComplexStruct: Decodable {
      let swifter: Swifter
      let lovesSwift: Bool
    }
    
    let json = """
    {
        "swifter": {
            "fullName": "Federico Zanetello",
            "id": 123456,
            "twitter": "http://twitter.com/zntfdr"
        },
        "lovesSwift": true
    }
    """.data(using: .utf8)! // our data in native format
    
    let myMoreComplexStruct = try JSONDecoder().decode(MoreComplexStruct.self, from: json)
    
    print(myMoreComplexStruct.swifter) // decoded!!!!!
    

    结尾

    为了避免必要的情况,也为了增强代码的健壮性,我们应该多使用系统的错误处理来避免经常崩溃:

    do {
      let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // do your decoding here
    } catch {
      print(error) // any decoding error will be printed here!
    }
    

    相关文章

      网友评论

      本文标题:Swift4 终极解析方案:基础篇

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