Swift4 终极解析方案:基础篇

转载请注明,原文地址: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!
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,429评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 昨天没有发,今天的话生物1.5h 数学20min明天继续写 看书3h
    bia唧妈阅读 247评论 0 0
  • 我不做太遥远的梦,这让我的睡眠安恬;我不去缅怀往事,因为回不到过去;我小心地去爱别人,因为我不爱泛滥。我想哭的时候...
    高冷的小小樱阅读 181评论 0 1