SwiftyJSON源代码学习(二)

作者: YinSJ | 来源:发表于2017-03-09 13:20 被阅读0次

    概述

    本文正式开始分析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为假说明是遍历字典时递归调用的,也就是说两个结构体的最外层结构是一样,只不过里层的结构不同,那么把里面的数据直接替换为新的数据

    相关文章

      网友评论

        本文标题:SwiftyJSON源代码学习(二)

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