SwiftyJSON 阅读笔记(1)

966 阅读3分钟

这是我参与更文挑战的第17天,活动详情查看: 更文挑战

SwiftyJSON 是一个 Swift 库,用于更好的处理 JSON 数据。它的核心代码+注释也就 1400 行左右。

下面是我在阅读源码过程中的收获。

实现原理

SwiftyJSON 的核心是一个 struct: JSON。JSON 定义的所有类型为:

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

对所有支持的类型,在 JSON 内部都封装了一个相应类型的属性来保存值。在修改值时,还会保存值对应的类型。获取值时,就会根据不同的类型读取不同的属性。

JSON 实现了 Swift.Collection 协议,所以我们可以像集合一样使用 JSON。在其内部就是根据不同的类型,调用相应属性(数组或者字典)的方法。

extension JSON: Swift.Collection {
    // 根据不同的类型,调用相应的属性(rawArray 或者 rawDictionary)的方法
}

判断 Any 的具体类型

之前我在判断 Any 对应的准备类型时,使用的是 if let data = object as? Data {},多种类型时,就会有多个 if let。SwiftyJSON 中使用的是 switch case,感觉更加的清晰。

public init(_ object: Any) {
    switch object {
    case let object as Data:
        // object 为 Data 类型
    default:
        self.init(jsonObject: object)
    }
}

NSNumber 是由 Bool 还是数值创建的?

NSNumber 可以通过 Bool 值创建,也可以通过各种类型的数值创建。创建完成之后,如何知道 NSNumber 是由 Bool 还是数值创建的?

我之前没有思考过这个问题,总是在需要 Bool 时直接转成 Bool,需要数值时直接转成数值,并没有准确的去判断 NSNumber 究竟是用来表示哪种类型的数据。

SwiftyJSON 中有一段代码用来判断 NSNumber 是否为 Bool 类型:

private let trueNumber = NSNumber(value: true)
private let falseNumber = NSNumber(value: false)
private let trueObjCType = String(cString: trueNumber.objCType)
private let falseObjCType = String(cString: falseNumber.objCType)

// MARK: - NSNumber: Comparable

extension NSNumber {
    fileprivate var isBool: Bool {
        let objCType = String(cString: self.objCType)
        if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) {
            return true
        } else {
            return false
        }
    }
}

先通过 compare() 方法判断是否和 Bool 值创建的两个 NSNumber 常量相等,再判断 objCType 是否一致。

NSNumber 的 objCType 属性返回一个 C 字符串,其中包含 NSNumber 对象中包含的数据的 Objective-C 类型。这个字符串由 @encode() 编译器指令编码。

下面打印出几个 objCType 看看。

let trueObjCType = String(cString: NSNumber(value: true).objCType)
let falseObjCType = String(cString: NSNumber(value: false).objCType)
let intObjCType = String(cString: NSNumber(value: 1).objCType)
let doubleObjCType = String(cString: NSNumber(value: 1.1).objCType)

debugPrint(trueObjCType)
// 输出 "c"
debugPrint(falseObjCType)
// 输出 "c"
debugPrint(intObjCType)
// 输出 "q"
debugPrint(doubleObjCType)
// 输出 "d"

在 Objective-C 中,我们可以通过给 @encode 传入一个类型,来获取代表这个类型的编码的 C 字符串:

char *typeChar1 = @encode(int32_t);
char *typeChar2 = @encode(NSArray);
// typeChar1 = "i", typeChar2 = "{NSArray=#}"

当我们需要知道 NSNumber 的准确类型时,就可以通过 objCType 获取类型了,然后和对应的编码进行比对。

另外 NSValue也有 objCType 属性,可以帮助我们获取准确的类型。

参考

类型编码 @ENCODE