该篇文章分为两部分,第一部分翻译自SwiftyJSON的README文档,可以对SwiftyJSON的使用有个大体的了解。第二部分对SwiftyJSON的源代码进行抽丝剥茧般的详细解读。
第一部分 - README翻译
为什么Swift对JSON的处理不够友好?
Swift
是一门类型安全的语言,这也就意味着 Swift
对于类型的处理必然十分严格。虽然明确的类型能够为我们避免许多错误的发生,然而,对于 Swift
中 JSON
的处理以及其他一些隐含类型的操作,也就变得异常痛苦。
我们拿 Twitter
的一个 API
来举例。通过这个 API,我们可以获取到关于 Timeline
的 JSON
数据,然后从这一坨 JSON
数据中获取第一条 Timeline
的 用户名
。代码如下:
if let JSONObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]],
let user = JSONObject[0]["user"] as? [String: Any],
let username = user["name"] as? String {
// Finally we got the username
}
很明显,这种写法并不友好。
即使我们采用如下的 Optinal
链式取值,也依然不忍直视:
if let JSONObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]],
let username = (JSONObject[0]["user"] as? [String: Any])?["name"] as? String {
// Finally we got the username
}
来看看采用 SwiftyJSON
后的代码是多么的优雅吧!
let JSONObject = JSON(data: dataFromNetworking)
if let userName = JSONObject[0]["user"]["name"].string {
// Simply we got the username
}
并且,我们完全不需要担心 Optional
解包以及数组越界,SwiftyJSON
已经都为我们考虑好了。
let JSONObject = JSON(data: dataFromNetworking)
if let userName = JSONObject[999999]["wrong_key"]["wrong_name"].string {
// The ".string" property still produces the correct Optional String type with safety
} else {
// Print the error
print(JSONObject[999999]["wrong_key"]["wrong_name"])
}
具体使用
初始化
import SwiftyJSON
// dataFromNetworking的类型是Data
let JSONObject = JSON(data: dataFromNetworking)
// jsonObject的类型是[JSON]、[String: JSON]、Data以及JSONObject
// JSONObject即NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull
let JSONObject = JSON(jsonObject)
下标
// Getting a double from a JSON Array
let name = json[0].double
// Getting an array of string from a JSON Array
let arrayNames = json["users"].arrayValue.map({$0["name"].stringValue})
// Getting a string from a JSON Dictionary
let name = json["name"].stringValue
// Getting a string using a path to the element
let path: [JSONSubscriptType] = [1, "list", 2, "name"]
let name = json[path].string
// Just the same
let name = json[1]["list"][2]["name"].string
// Alternatively
let name = json[1, "list", 2, "name"].string
循环
// If json is Dictionary
for (key, subJSON): (String, JSON) in json {
// Do something you want
}
// If json is Array
// The index is 0..<json.count's string value
for (index, subJSON): (String, JSON) in json {
// Do something you want
}
// 不管是Dictionary还是Array,元祖中的第一个元素都是String类型
错误
对于 Swift
中的常规数据类型,在进行 Getter
或 Setter
操作的时候,如果是
Array
,可能会由于越界产生崩溃。Dictionary
,可能会莫名奇妙的产生了nil
。- 既不是
Array
也不是Dictionary
,可能会产生unrecognised selector
异常导致App崩溃。
然而在 SwiftyJSON
下,这些都不会发生。
// 使用Array进行初始化
let json = JSON(["name", "age"])
if let name = json[999].string {
// Do something you want
} else {
print(json[999].error) // "Array[999] is out of bounds"
}
// 使用字典进行初始化
let json = JSON(["name": "Jack", "age": 25])
if let name = json["address"].string {
// Do something you want
} else {
print(json["address"].error) // "Dictionary["address"] does not exist"
}
// 使用Int进行初始化
let json = JSON(12345)
if let age = json[0].string {
// Do something you want
} else {
print(json[0]) // "Array[0] failure, It is not an array"
print(json[0].error) // "Array[0] failure, It is not an array"
}
if let name = json["name"].string {
// Do something you want
} else {
print(json["name"]) // "Dictionary["name"] failure, It is not an dictionary"
print(json["name"].error) // "Dictionary["name"] failure, It is not an dictionary"
}
Optinal取值
// NSNumber
if let id = json["user"]["favourites_count"].number {
// Do something you want
} else {
// Print the error
print(json["user"]["favourites_count"].error)
}
// String
if let id = json["user"]["name"].string {
// Do something you want
} else {
// Print the error
print(json["user"]["name"])
}
// Bool
if let id = json["user"]["is_translator"].bool {
// Do something you want
} else {
// Print the error
print(json["user"]["is_translator"])
}
// Int
if let id = json["user"]["id"].int {
// Do something you want
} else {
// Print the error
print(json["user"]["id"])
}
非Optinal取值(直接取值,若无值,则赋默认值)
// If not a Number or nil, return 0
let id: Int = json["id"].intValue
// If not a String or nil, return ""
let name: String = json["name"].stringValue
// If not an Array or nil, return []
let list: Array<JSON> = json["list"].arrayValue
// If not a Dictionary or nil, return [:]
let user: Dictionary<String, JSON> = json["user"].dictionaryValue
Setter
json["name"] = JSON("new-name") // [String: JSON]
json[0] = JSON(1) // [JSON]
json["id"].int = 1234567890
json["coordinate"].double = 8766.766
json["name"].string = "Jack"
json.arrayObject = [1, 2, 3, 4]
json.dictionaryObject = ["name": "Jack", "age": 25]
原始对象
// Convert the JSON to raw Data
if let data = json.rawData() {
// Do something you want
}
// Convert the JSON to a raw String
if let string = json.rawString() {
// Do something you want
}
值是否存在
// Shows you whether value specified in JSON or not
if json["name"].exists()
字面量转换
Swift
为我们提供了一组非常有意思的协议,用来将字面量转换为特定的类型。对于那些实现了字面量转换协议的类型,在提供字面量赋值的时候,就可以简单地按照协议中定义的规则通过赋值的方式将值直接转换为对应类型。在实际开发中我们经常可能用到的有:NilLiteralConvertible
, ArrayLiteralConvertible
, DictionaryLiteralConvertible
, BooleanLiteralConvertible
, IntegerLiteralConvertible
, FloatLiteralConvertible
, StringLiteralConvertible
// NilLiteralConvertible
let json: JSON = nil
// ArrayLiteralConvertible
let json: JSON = ["I", "am", "a", "json"]
// DictionaryLiteralConvertible
let json: JSON = ["I": "am", "a": "json"]
// BooleanLiteralConvertible
let json: JSON = true
// IntegerLiteralConvertible
let json: JSON = 12345
// FloatLiteralConvertible
let json: JSON = 2.8765
// StringLiteralConvertible
let json: JSON = "I'm a json"
// With subscript in array
var json: JSON = [1, 2, 3]
json[0] = 100
json[1] = 200
json[2] = 300
json[999] = 300 // Don't worry, nothing will happen
// With subscript in dictionary
var json: JSON = ["name": "Jack", "age": 25]
json["name"] = "Mike"
json["age"] = "25" //It's OK to set String
json["address"] = "L.A." // Add the "address": "L.A." in json
// Array & Dictionary
var json: JSON = ["name": "Jack", "age": 25, "list": ["a", "b", "c", ["what": "this"]]]
json["list"][3]["what"] = "that"
json["list", 3, "what"] = "that"
let path: [JSONSubscriptType] = ["list", 3, "what"]
json[path] = "that"
// With other JSON objects
let user: JSON = ["username": "Steve", "password": "123456"]
let auth: JSON = [
"user": user.object // use user.object instead of just user
"apiKey": "token123456"
]
合并
let original: JSON = [
"first_name": "John",
"age": 20,
"skills": ["Coding", "Reading"],
"address": [
"street": "Front St",
"zip": "12345",
]
]
let update: JSON = [
"last_name": "Doe",
"age": 21,
"skills": ["Writing"],
"address": [
"zip": "12342",
"city": "New York City"
]
]
let updated = original.merge(with: update)
/*
[
"first_name": "John",
"last_name": "Doe",
"age": 21,
"skills": ["Coding", "Reading", "Writing"],
"address": [
"street": "Front St",
"zip": "12342",
"city": "New York City"
]
]
*/
第二部分 - SwiftyJSON源码解读
在写本篇博客的时候,SwiftyJSON最新的Release版本为3.1.4,下面就基于此版本的源码来细读一下。
把最新的 Release
版本下载下来并解压缩,双击文件夹中的 SwiftyJSON.xcworkspace
打开工程,在 Source
文件夹下,可以看到 SwiftyJSON
库就只有两个源码文件,分别为 SwiftyJSON.h
和 SwiftyJSON.swift
。
先来看 SwiftyJSON.h
文件:
@import Foundation;
//! Project version number for SwiftyJSON.
FOUNDATION_EXPORT double SwiftyJSONVersionNumber;
//! Project version string for SwiftyJSON.
FOUNDATION_EXPORT const unsigned char SwiftyJSONVersionString[];
显而易见,这几行 Objective-C
代码仅仅是描述了 SwiftyJSON
库的版本号。并且如果你在自己的工程中是通过 Pod
方式引入 SwiftyJSON
库的话,是看不到 SwiftyJSON.h
文件的。
那么,我们就来好好看看 SwiftyJSON.swift
这个文件吧!!!
从上面第一部分的 README
可以看出,SwiftyJSON
库的功能还是非常强大的,能够极大的方便我们操作JSON数据。然而可能会出乎你意料的是,这么强大的库仅仅只用了 1475
行代码就实现了,用 cloc
工具可以更细致的统计一下,如下图,扣除空行和代码注释,SwiftyJSON.swift
需要阅读的代码只剩下 1154
行了,这也就相当于两个略微复杂页面的 ViewController
代码量吧。
接下来我们就正式开始阅读源码了,那从什么地方开始看起来呢?一般来说总是从初始化代码开始。不过在这里,为了能够进一步快速缩小代码行数,我准备从最简单且代码量较大的地方着手,因为这样一来,既可以由浅入深,又能在心理上造成看的很快的错觉。
那最简单且代码量又较大的一部分在哪里呢?花10秒钟快速浏览一下代码结构,我们很本能的就会定位到 1036
行到 1308
行的 extension JSON { ... }
。这块纯代码有 242
行,主要定义了 24
个计算性属性,分别为:
double
和doubleValue
float
和floatValue
int
和intValue
,uInt
和uIntValue
int8
和int8Value
,uInt8
和uInt8Value
int16
和int16Value
,uInt16
和uInt16Value
int32
和int32Value
,uInt32
和uInt32Value
int64
和int64Value
,uInt64
和uInt64Value
很显然,这 24
个计算型属性几乎都以类似 int
和 intValue
这种形式成对出现,那就挑一对看下都表达了些什么意思。
public var double: Double? {
get {
return self.number?.doubleValue
}
set {
if let newValue = newValue {
self.object = NSNumber(value: newValue)
} else {
self.object = NSNull()
}
}
}
public var doubleValue: Double {
get {
return self.numberValue.doubleValue
}
set {
self.object = NSNumber(value: newValue)
}
}
double
和 doubleValue
作为可读可写的计算型属性都提供了 Getter
和 Setter
方法并以 public
修饰来暴露给开发者调用。他们的区别在于,double
返回的是一个 Double?
的可选类型,而 doubleValue
则直接返回 Double
类型。
从两者的 Setter
方法中可以更加直观的看到,对于 double
来说, json[0].double = 3.14
以及 json[0].double = nil
都是合法的,而对于 doubleValue
来说,json[0].doubleValue = 3.14
没毛病,但是 json[0].doubleValue = nil
则会导致编译器报错。
这里有必要稍微解释一下 self.object
是个什么东西,其定义在 SwiftyJSON.swift
文件的第 222
行,如下(只是稍微解释一下,所以暂时省略一些不必要的代码):
/// Object in JSON
public var object: Any {
get { ... }
set {
switch newValue {
case let number as NSNumber:
case let string as String:
case _ as NSNull:
case _ as [JSON]:
case nil:
case let array as [Any]:
case let dictionary as [String : Any]:
default:
}
}
}
object
是 JSON
结构体中的又一个计算型属性,其 Setter
方法的作用是把原始的 Any
类型转换为 JSON
类型,换句话说,一旦执行 self.object = xxx
这行代码,就把 Any
类型的 xxx
转化为了 JSON
类型。
那 SwiftyJSON
都支持哪些类型可以转化为 JSON
类型呢?从 Setter
方法中可以看到,支持的类型有:NSNumber
,String
,[Any]
,[String: Any]
,[JSON]
,NSNull
,nil
。
扯完了 double
和 doubleValue
的 Setter
方法,下面就该说说 Getter
方法了。
double
返回的是 self.number?.doubleValue
,doubleValue
返回的是 self.numberValue.doubleValue
。 很明显,它们主要的差别在于 number
和 numberValue
,来到 SwiftyJSON.swift
文件的第 939
行,可以看到 number
和 numberValue
的定义,如下:
// Optional number
public var number: NSNumber? {
get {
switch self.type {
case .number:
return self.rawNumber
case .bool:
return NSNumber(value: self.rawBool ? 1 : 0)
default:
return nil
}
}
set {
self.object = newValue ?? NSNull()
}
}
// Non-optional number
public var numberValue: NSNumber {
get {
switch self.type {
case .string:
let decimal = NSDecimalNumber(string: self.object as? String)
if decimal == NSDecimalNumber.notANumber { // indicates parse error
return NSDecimalNumber.zero
}
return decimal
case .number:
return self.object as? NSNumber ?? NSNumber(value: 0)
case .bool:
return NSNumber(value: self.rawBool ? 1 : 0)
default:
return NSNumber(value: 0.0)
}
}
set {
self.object = newValue
}
}
同样的,又是两个计算型属性,并且,与 double
和 doubleValue
类似,一个为 NSNumber?
的可选类型,一个为 NSNumber
非可选类型。而 NSNumber
类型中有一个 doubleValue
的只读属性,返回的就是 Double
类型的数据。这里有一点需要说明并注意区分一下,JSON
类型的 doubleValue
和 NSNumber
类型的 doubleValue
是不一样的两个东西。
先说说简单的 Setter
方法吧,虽然 number
和 numberValue
属性都提供了可写的功能,但是从整个源码中并未发现其作用,也就是说,这两个 Setter
方法可以直接去掉,至于作者为什么要写这几行代码,也许是之前版本的遗留,也许是为了以后版本的扩展,也许是纯粹为了不让 Getter
方法孤独,who knows?
重点看下 Getter
方法,先看略微复杂一点的 numberValue
,根据 self.type
枚举值的不同,若能转化为 NSNumber
的,则返回具体的 NSNumber
,不能转化的,则返回 NSNumber(value: 0.0)
。
这样一来,从最初的 1154
扣除上面差不多 280
行代码,还剩下 874
行代码了。