SwiftyJSON README翻译及源码解读

SwiftyJSON_01

该篇文章分为两部分,第一部分翻译自SwiftyJSON的README文档,可以对SwiftyJSON的使用有个大体的了解。第二部分对SwiftyJSON的源代码进行抽丝剥茧般的详细解读。


第一部分 - README翻译

为什么Swift对JSON的处理不够友好?

Swift 是一门类型安全的语言,这也就意味着 Swift 对于类型的处理必然十分严格。虽然明确的类型能够为我们避免许多错误的发生,然而,对于 SwiftJSON 的处理以及其他一些隐含类型的操作,也就变得异常痛苦。

我们拿 Twitter 的一个 API 来举例。通过这个 API,我们可以获取到关于 TimelineJSON 数据,然后从这一坨 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 中的常规数据类型,在进行 GetterSetter 操作的时候,如果是

  • 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.hSwiftyJSON.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 代码量吧。

SwiftyJSON_02

接下来我们就正式开始阅读源码了,那从什么地方开始看起来呢?一般来说总是从初始化代码开始。不过在这里,为了能够进一步快速缩小代码行数,我准备从最简单且代码量较大的地方着手,因为这样一来,既可以由浅入深,又能在心理上造成看的很快的错觉。

那最简单且代码量又较大的一部分在哪里呢?花10秒钟快速浏览一下代码结构,我们很本能的就会定位到 1036 行到 1308 行的 extension JSON { ... }。这块纯代码有 242 行,主要定义了 24 个计算性属性,分别为:

  • doubledoubleValue
  • floatfloatValue
  • intintValueuIntuIntValue
  • int8int8ValueuInt8uInt8Value
  • int16int16ValueuInt16uInt16Value
  • int32int32ValueuInt32uInt32Value
  • int64int64ValueuInt64uInt64Value

很显然,这 24 个计算型属性几乎都以类似 intintValue 这种形式成对出现,那就挑一对看下都表达了些什么意思。

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)
    }
}

doubledoubleValue 作为可读可写的计算型属性都提供了 GetterSetter 方法并以 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: 
        }
    }
}

objectJSON 结构体中的又一个计算型属性,其 Setter 方法的作用是把原始的 Any 类型转换为 JSON 类型,换句话说,一旦执行 self.object = xxx 这行代码,就把 Any 类型的 xxx 转化为了 JSON 类型。
SwiftyJSON 都支持哪些类型可以转化为 JSON 类型呢?从 Setter 方法中可以看到,支持的类型有:NSNumberString[Any][String: Any][JSON]NSNullnil

扯完了 doubledoubleValueSetter 方法,下面就该说说 Getter 方法了。

double 返回的是 self.number?.doubleValuedoubleValue 返回的是 self.numberValue.doubleValue。 很明显,它们主要的差别在于 numbernumberValue,来到 SwiftyJSON.swift 文件的第 939 行,可以看到 numbernumberValue 的定义,如下:

// 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
    }
}

同样的,又是两个计算型属性,并且,与 doubledoubleValue 类似,一个为 NSNumber? 的可选类型,一个为 NSNumber 非可选类型。而 NSNumber 类型中有一个 doubleValue 的只读属性,返回的就是 Double 类型的数据。这里有一点需要说明并注意区分一下,JSON 类型的 doubleValueNSNumber 类型的 doubleValue 是不一样的两个东西。
先说说简单的 Setter 方法吧,虽然 numbernumberValue 属性都提供了可写的功能,但是从整个源码中并未发现其作用,也就是说,这两个 Setter 方法可以直接去掉,至于作者为什么要写这几行代码,也许是之前版本的遗留,也许是为了以后版本的扩展,也许是纯粹为了不让 Getter 方法孤独,who knows?
重点看下 Getter 方法,先看略微复杂一点的 numberValue,根据 self.type 枚举值的不同,若能转化为 NSNumber 的,则返回具体的 NSNumber,不能转化的,则返回 NSNumber(value: 0.0)

这样一来,从最初的 1154 扣除上面差不多 280 行代码,还剩下 874 行代码了。