SwiftyJSON源代码学习(二)

概述

本文正式开始分析SwiftyJSON。

原生的JSONSerialization已经实现了高效的DataAny相互转化,所差的只是一种灵活方便的方式,进行错误处理和对Any对象进行转型。SwiftyJSON的方案是,设计一个JSON结构体,该结构体有一个type属性来对应JSON的6种数据类型,每种类型都有一个相应的Swift属性与之绑定。通过递归的对解析后的Any类型对象进行解包,判断出每一层的数据类型并将确定完类型的对象赋值到JSON结构体的object属性中。然后通过Swift的Subscript特性,以下标的方式让用户获取JSON结构体中嵌套数据。最后通过Swift的RawRepresentable特性,来获取最终的Swift对象。

代码充分利用了Swift “面向协议编程” 和其他语言特性,可以说非常的Swifty,下面我们来一一分析和学习。
首先是第一个部分,JSON结构体的设计和实现,在此之前先简单的回顾一下JSON的定义和苹果的JSONSerialization

关于JSON

JSON全称JavaScript Object Notation, 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。

JSON有6种数据格式:

  • object,格式为{ string: value, string: value },如{ "code": 0, "version": "2.5"}
    用以表示一个无序的“名称/值”对的集合,对应于Swift中的 Dictionary
  • array,格式为[ value, value ],如 [ "foo", "bar" ]
    用以表示一个有序的值的集合,对应于Swift中的Array
  • string,格式为 " string ",如 "foo"
    用以表示一个字符串,对应于Swift中的String
  • number,如 123
    用以表示一个数字,对应于Swift中的数值类型,如Int Double UInt
  • truefalse
    用以表示布尔值,对应Swift中的Bool
  • null
    用以表示空,对应Swift中的nil

JSON的最外层必须是objectarray,内部可以以多层objectarray嵌套的形式来表示复杂的数据结构

关于JSONSerialization

JSONSerialization是苹果用于序列化/反序列化JSON数据的类,其可以实现JSON的二进制数据DataAny对象的相互转化。由于Data并不一定能反序列化为Any对象,所以需要进行错误处理。由于得到的是Any类型,所以当实际使用的时候,还必须进行多次的转型和判断。

do {
  let jsonObject: Any = try JSONSerialization.jsonObject(with: data, options: [])
} catch {
  print("解析失败")
}

SwiftyJSON中的JSON结构体的设计

从文章开始的分析中可以得知,该JSON结构体需要有Any类型的object属性来储存从Data反序列化后的结果,一个枚举type属性来表示该object的类型,以及一个NSError类型的error属性来进行错误处理。
其流程为:

  1. 假设要传输的JSON为{ “user” : “ysj”},我们从网络接收到的数据将是一个17字节的Data
  2. 通过JSONSerialization反序列化后,如果没有错误发生,将得到一个Any对象,此时我们并不知道它里面具体是什么;
  3. Any对象递归的解包之后,就得到了unwrapedObject,即字典["user": "ysj"]
  4. 根据unwrapedObject的类型,对结构体的typerowValue赋值,以方便后续的使用
  5. 以计算属性objecttyperowValue进行封装,方便外部对JSON结构体数据的使用
关于StructClass的选择

在Swift中,结构体不仅可以定义方法实现逻辑,还可以通过遵守、实现协议来获得更多的特性。而且对于不需要获得OC特性的类型来说,也不再需要声明为NSObject的子类,可以说,不能再以OC中的结构体和类的概念来思考自定义类型。
Swift中的StructClass的最大区别有两个,可以根据这两大区别来灵活选择使用哪一种

  • Struct是值类型,Class是引用类型。也就是说,在进行赋值的时候:Struct是深复制,其结果是实例的一个全新的副本;而Class是浅复制,其结果是原实例的一个新的引用。当一个实例传递到多处使用时,尤其是在多线程的条件下,由于Struct的值类型特点,我们可以不用担心非预期的数据修改、多线程数据竞争
  • Struct只是简单的数据/方法集合体,虽然可以通过方法和协议获得强大的能力,但并不支持继承、多态、引用计数等功能。

知识点三:自定义类型时,不要不加思索的使用Class。如果在传递时更倾向于生成一个新的副本,并且本身不需要面向对象的特性,应使用Struct来获得更多的线程安全和内存安全。

JSON结构体的属性

关于Swift中的属性

Swift有两种属性:

  • 存储属性(Stored Properties)


就是我们平常使用的普通属性,它直接用来存储数据。根据Swift的初始化规则,每个非optional的属性都必须进行初始化,所以一般直接在声明的属性以字面值、构造器或闭包的方式赋值。

比较特殊的是以lazy关键字修饰的“懒加载属性”,这种属性不会和实例一起完成初始化,而是在第一次被使用的时候再初始化。其好处一是可以节省资源、加快实例化的速度;二是可能某些属性需要对象被实例化后才能确定(官方教材的说法,我没想出来应用场景,欢迎同学们分享你的idea)。

  • 计算属性(Computed Properties)


其数据是依靠其他的属性通过一定的逻辑处理来确定的。通过在属性后加大括号并在里面添加set get方法来实现。如果属性需要被声明为只读,可以只添加get方法而不添加set方法,此时的get { }可以省略,直接return数据即可。

关于JSON结构体的属性
  • 私有的枚举Type类型的属性_type,来对应6种JSON数据格式,考虑到存在非法格式的可能性,另增加了unknown类型。
  • 私有的与Type相对应6个属性rawValue,用于存储进行过类型转换后的最终结果
  • 私有的NSError?类型的_error属性用于储存在解析过程中出现的错误
  • 暴露给外部使用的object计算属性,其setter方法将对给定的Any对象解包后根据类型对typerawValue赋值 ,其getter方法将根据类型type返回相应的rawValue
  • 暴露给外部使用的typeerror属性。外部可能需要结构体的TypeError信息,但是不应赋予其写的权限。因此声明两个只读的计算属性typeerror,直接返回_type_error的值

知识点四:根据实际需要灵活使用存储属性(包括懒加载)和计算属性。如果有私有存储属性需要给外部提供只读接口,可以把私有属性命名为_name的形式,并增加一个以name命名的只读计算属性,直接返回_name的值

JSON结构体的初始化方法

初始化

从输入考虑,有如下可能的情况:二进制数据Data,JSON字符串String,任意类型Any。虽然有多种可能的输入,但是最终的逻辑是一样的,即通过一个解析后的Any对象,生成一个JSON结构体。我们可以把最终生成JSON结构体的逻辑封装成一个指定初始化方法(Designated Initializer),其他的所有初始化方法则是对输入进行相应的处理后,再代理给指定初始化方法。(严格来说,指定初始化方法是作用于Class,相对于Convenience Initializers来说的,但是其思想同样可以用于Struct)

这样的设计思路有几个好处,一是把初始化的逻辑拆分成了两大块,降低了复杂度;二是提高了代码的复用性和扩展能力

具体的初始化方法分析如下:

  1. 私有的指定初始化方法,通过参数jsonObject创建JOSN结构体实例并初始化object属性
  2. 二进制数据Data。调用系统的JSONSerializationData进行解析,将解析后的jsonObject对象代理给指定初始化方法进行初始化;如果有错误产生,那么通过指定初始化方法构造一个空JSON,并把错误保存到属性error
  3. JSON字符串String。通过字符串创建Data,再代理给「情况2」进行初始化,如果创建失败,则通过指定初始化方法构造一个空JSON
  4. 任意类型Any。因为此Any不一定是JSONSerialization解析后的Any,还有可能是Data类型。所以进行一次判断,如果是Data则代理给「情况2」进行初始化,否则代理给指定初始化方法进行初始化

知识点五: 对初始化方法的设计,应在分析可能的参数和初始化逻辑的基础上,设计出指定初始化方法,形成一个初始化代理链,以降低初始化方法的复杂度,提高复用和扩展性

合并

除了初始化外,还有一种需要创建JSON结构体实例的情况是,合并两个JSON结构体。方法代码截图如下:


方法中使用了mutating关键字,其作用是使得方法可以修改调用该方法的实例本身,即self

联想一下Swift数组排序的两个方法sort()sorted()sort()的方法声明mutating func sort()sorted()的方法声明func sorted() -> [Self.Iterator.Element]。前者是直接修改数组,后者是返回一个新的数组。同时苹果对这种方法的命名规则也是很值得学习的,以被动语态表示这是一个经过处理后的新实例。

设想一下使用场景,相比返回一个新的实例,直接修改原来的实例可以不用声明一个新的变量或重新赋值,无疑更适合我们的情况。

知识点六:设计操作实例的方法时,除了返回一个新的实例,也可以通过mutating关键字直接修改实例本身,命名时通过方法的被动/主动语态区分两者。

合并逻辑:

  1. 如果两个结构体的type相同:
  • 如果是除字典和数据外的类型,直接替换为新的数据
  • 如果是数组,把两个数组的内容合并
  • 如果是字典,遍历字典,如果key相同,递归的调用合并方法把对应的value进行合并,并附加一个标志typecheck
  1. 如果两个结构体的type不同:
  • 如果typecheck为真说明是第一次调用,也就是说两个结构体的最外层结构是不相同的,那么无法合并,抛出定义好的NSError错误
  • 如果typecheck为假说明是遍历字典时递归调用的,也就是说两个结构体的最外层结构是一样,只不过里层的结构不同,那么把里面的数据直接替换为新的数据
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,784评论 1 10
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 2016年10月12日 Objective-C id为Swift Any Swift 3接口与Objective-...
    魔灵FH阅读 2,852评论 0 19
  • 这世上并不是每一份付出都会有收获的,很幸运的是,我的付出有了收获。 对于运动,我一直都是随机而盲目。不科学运动的后...
    一念见花开阅读 169评论 0 0