首页投稿(暂停使用,暂停投稿)

SwiftyJSON源代码学习(二)

2017-03-09  本文已影响0人  YinSJ

概述

本文正式开始分析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种数据格式:

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的最大区别有两个,可以根据这两大区别来灵活选择使用哪一种

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

JSON结构体的属性

关于Swift中的属性

Swift有两种属性:


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

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


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

知识点四:根据实际需要灵活使用存储属性(包括懒加载)和计算属性。如果有私有存储属性需要给外部提供只读接口,可以把私有属性命名为_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相同:
  1. 如果两个结构体的type不同:
上一篇下一篇

猜你喜欢

热点阅读