SwiftyJSON 源码解析 —— 一款 Swift 的 JSON 解析框架

发布于:2016-10-30 16:09,阅读数:2546,点赞数:13


> [「SwiftyJSON」](https://github.com/SwiftyJSON/SwiftyJSON)是一款由 Swift 语言开发的 JSON 解析开源框架。

# 导语

SwiftyJSON 大概是我接触的第一个 Swift 框架。当时的 Swift 是 1.1 版本,Xcode 还是“能用”的 Xcode。看看现在的 Xcode,抹眼泪。

它将JSON字符串中不同类型数据进行了统一的封装,暴露简单接口,利用非常人性化“路径”就可以在JSON对象中取到想要的内容。下面就来简单解析一下源码。

# 构造

在SwiftyJSON中,JSON的值类型只有这几种情况:

```swift
public enum Type :Int{
case number
case string
case bool
case array
case dictionary
case null
case unknown
}
```

SwiftyJSON在构造时需要对这几种不同的值进行封装,统一接口。下面就来看看他是如何封装的。

其主要有四个构造方法(还有一些`LiteralConvertible`的构造方法,在协议部分提到):

```swift
public init(data:Data, options opt: JSONSerialization.ReadingOptions = .allowFragments, error: NSErrorPointer = nil)
public init(_ object: Any)
public init(_ jsonArray:[JSON])
public init(_ jsonDictionary:[String: JSON])
```

除此之外还有一个从字符串构造JSON的parse方法:

```swift
public static func parse(_ string:String) -> JSON
```

和一个构造空JSON的static属性:

```swift
@available(*, unavailable, renamed:"null")
public static var nullJSON: JSON { get { return null } }
public static var null: JSON { get { return JSON(NSNull()) } }
```

上面一共六个构造方法在经过不同的处理以后,最终都指向了同一个地方:

```swift
public var object: Any { get{ ... } set { ... } }
```

在这个object的set方法中,对传入的对象进行了类型筛选和保存。SwiftyJSON中真正保存数据的是:

```swift
fileprivate var rawArray: [Any] = []
fileprivate var rawDictionary: [String : Any] = [:]
fileprivate var rawString: String = ""
fileprivate var rawNumber: NSNumber = 0
fileprivate var rawNull: NSNull = NSNull()
fileprivate var rawBool: Bool = false
```

object的set方法根据不同的类型将数据分别填入了这几个属性中进行保存,同时设置了以下两个属性,一个用来保存当前JSON的数据类型,一个用来保存set过程以及存取过程中发生的错误:

```swift
fileprivate var _type: Type = .null
fileprivate var _error: NSError? = nil
```

SwiftyJSON经过这个步骤,将不同属性的数据类型封装了起来。保存数据的属性全都是`fileprivate`的,对于用户来说,这些我们都不必去关心。

# 下标

SwiftyJSON中很炫酷一点的就是可以这样写路径,根据路径一层一层的找到想要的值。最重要的是,就算值不存在,也不用担心程序crash:

```swift
let name = json[1]["list"][2]["name"].string
```

在Swift中实现下标十分容易,只需要实现`subscript `方法就可以了。在JSON中,索引有两种数据类型,`Int`和`String`,因此下标需要支持这两种类型。那么在SwiftyJSON中这是怎么实现的呢?

- 使用枚举(`enum`)定义数据类型
在Swift中,枚举是个很强大的数据结构。索引的类型有两种情况,因此其类型为:

```swift
public enum JSONKey {
case index(Int)
case key(String)
}
```

- 定义`JSONSubscriptType`协议

```swift
public protocol JSONSubscriptType {
var jsonKey:JSONKey { get }
}
```

- 使用扩展让`Int`和`String`遵守协议

```swift
extension Int: JSONSubscriptType {
public var jsonKey:JSONKey {
return JSONKey.index(self)
}
}
extension String: JSONSubscriptType {
public var jsonKey:JSONKey {
return JSONKey.key(self)
}
}
```

定义`JSONSubscriptType`协议的优点在于在写法上能统一`Int`和`String`。语法糖,它们都遵守了这个协议,因此就可以这样接收参数:

```swift
fileprivate subscript(sub sub: JSONSubscriptType) -> JSON
public subscript(path: [JSONSubscriptType]) -> JSON
public subscript(path: JSONSubscriptType...) -> JSON
```

这三个方法将接收六种形态的参数。最终这三个方法将索引分发到下面两个方法:

```swift
fileprivate subscript(index index: Int) -> JSON
fileprivate subscript(key key: String) -> JSON
```

那么如果在索引过程中,索引是非法的会发生什么呢?例如在一个非`Array`的JSON中使用`Int`会发生什么呢?我们直接看源码:

```swift
fileprivate subscript(index index: Int) -> JSON {
get {
if self.type != .array {
var r = JSON.null
r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"])
return r
} else { ... }
}
set { ... }
}
```

如果当前JSON不是`Array`,将会返回一个空的JSON,而不是直接返回nil。如果接下来还有路径,将会继续返回空JSON,从而避免了非法索引可能造成的问题。

下标赋值时也是同理,由于`subscript`是`get & set`。下标赋值时也会按照上述的路径进行分发,最终到到`set`中进行赋值,如果路径是非法的,那么什么都不会发生。

# 其他协议

- `Collection`协议
`Collection`协议包含了`Indexable`和`Sequence`协议。由于数据类型中`Array`和`Dictionary`遵守了`Collection`协议,SwiftyJSON遵守`Collection`协议只为`Array`和`Dictionary`这两种数据类型服务。其余情况都将返回空JSON。

- `Comparable`协议
规定了JSON对象之间的比较方法,如果两端JSON真正数据类型不同将返回`false`。个人看来比较的意义不是很大,因为在不确定两个JSON对象真正数据类型的情况下进行比较,无法判断到底是比较结果是`false`还是因为类型不匹配而返回`false`。

- `LiteralConvertible`相关协议
SwiftyJSON遵守了一些`LiteralConvertible `相关的协议。这些协议扩展了一些构造方法,代表JSON对象也可以由`String`、`Int`系列、`Boolean`系列、`Float`系列等等这些数据类型构造。`RawRepresentable`协议中也扩展了利用raw数据进行构造的方法,这些构造方法的构造过程最终也是走了上面所说的`object`参数的`set`过程。

- `CustomStringConvertible`和`CustomStringConvertible`协议
自定义了`description`以及`debugDescription`字符串。

# 结语

SwiftyJSON的实现虽然简单但是十分实用。之后Alamofire推出了[「Alamofire-SwiftyJSON」](https://github.com/SwiftyJSON/Alamofire-SwiftyJSON)和SwiftyJSON协作,业务开发中的JSON网络请求一气呵成,提升了开发效率。使用开源框架固然重要,但更重要的是能从这些框架中学到什么。


评论:0条


返回列表

返回归档

返回主页