SwiftyJSON 阅读笔记(2)

798 阅读4分钟

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

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

其他文章

SwiftyJSON 阅读笔记(1)

字面量转换

在 SwiftyJSON 中,给 JSON 提供了丰富的初始化方法,可以快速的使用字面量来创建一个 JSON。比如:

let json = JSON(12345)
let json = JSON(["name", "age"])
let json = JSON(["name":"Jack", "age": 25])

Swift 提供了一组非常有意思的接口,用来将字面量转化成对应类型。在实现了这些转化接口之后,就可以简单的通过赋值的方式转换为对应的类型。字面量转换接口都定义了一个 typealias 和对应的 init 方法。

经常用到的接口有:

  • ExpressibleByStringLiteral:其中定义了 public typealias StringLiteralType = String,和对应的 init 方法。
  • ExpressibleByArrayLiteral
  • ExpressibleByBooleanLiteral
  • ExpressibleByDictionaryLiteral
  • ExpressibleByFloatLiteral
  • ExpressibleByNilLiteral
  • ExpressibleByIntegerLiteral

关于字面量转换的更多内容,推荐查看王巍的文章:字面量转换,虽然其中有一些过时的内容,但是还是有很多学习价值。

字面量转换是一把双刃剑,它可以有效的缩短代码,简洁的表意,但同时,因为没有明显的初始化函数,会让其他人感到困惑,也无法通过 cmd + 点击快速的跳转到对应的初始化方法。

如何在 Swift 的 dictionary 中添加 nil 值

看到 SwiftyJSON 中的一段代码,感觉很奇怪:

guard let dict = object as? [String: Any?] else {
        return nil
}
let body = try dict.keys.map { key throws -> String in
    guard let value = dict[key] else {
            return "\"\(key)\": null"
    }
    guard let unwrappedValue = value else {
            return "\"\(key)\": null"
    }

    let nestedValue = JSON(unwrappedValue)
    // ....
}
        

[String: Any?] 类型的字典中,想获取一个 key 对应的 value,需要两次解包么?value 可能为 nil 么?对一个 key 赋值为 nil 时,不是会直接清除掉对应的 key 么?

来看下面一段代码:

let value: Any? = nil
var dict = [String: Any?]()
dict["key"] = value
debugPrint(dict)

// 输出:["key": nil]

果然 [String: Any?] 类型的字典中的 value 可以为空。再看下面一段代码,并不是给一个 key 任意的赋值为 nil 值都可以保存在 dictionary 中。

let aKey = "aKey"
var dict = [String: Any?]()
let value: String? = nil

dict[aKey] = value
debugPrint(dict)
// 输出:[:]

dict[aKey] = value as Any?
debugPrint(dict)
// 输出:["aKey": nil]

只有 value 为 Any? 类型时,才保存在了 dictionary 中。

如何在 Swift 的 dictionary 中添加 nil?

方式一:符合对应的类型

和将任何其他类型的值添加到字典的方式相同。但是要保证字典可以保存对应的值类型。对一个值类型是可选类型的字典来说,比如 [String : Any?],那么它可以保存 nil 值。例如,

let x : [String : AnyObject?] = ["foo" : nil]

注意,[K:V] 类型的 dictionary,通过下标访问时,返回的类型为可选的 V?,可选意味着是否有该 key 对应的值,如果有值,则返回对应的值;通过下标访设置值时,可以设置一个值,或者通过设置 nil 来删除条目。

因此,对于 [String : Any?] 类型的字典,通过下标返回的类型是 Any??。同样,将值放入下标时,外部可选项允许设置值或删除条目(外部可选项是指 Any?? 中的第二个 ?)。如果我们简单地写:

x["foo"] = nil

编译器推断它是 Any?? 类型的 nil,外部可选意味着删除键 foo 的条目。

为了将键 foo 的值设置为 Any?值 nil,我们需要传入一个非 nil 的外部可选,即:包含一个值为 nil 的内部可选(Any? 类型)。对此,我们可以这样做:

let v: Any? = nil
x["foo"] = v

或者

x["foo"] = nil as Any?

这意味着设置的为 Any? 类型的 nil,而不是 Any??

方式二:updateValue

另外,还可以通过 updateValue 方法,在字典中添加 nil 值:

var dic = [String: Any?]()
dic.updateValue(nil, forKey: "foo")
debugPrint(dic)

// 输出:["foo": nil]

方式三:NSNull

能否可以使用 NSNull 来设置 nil 值呢?下面是使用 NSNull 和使用 Any? 的代码对比。

使用 NSNull 的代码:

let aKey = "aKey"
var dict = [String: Any?]()
dict[aKey] = NSNull()
debugPrint(dict)
// 输出:["aKey": Optional(<null>)]
debugPrint(type(of: dict[aKey]))
// 输出:Swift.Optional<Swift.Optional<Any>>
if dict[aKey] is NSNull {
    debugPrint("value is NSNull")
} else {
    debugPrint("value is not NSNull")
}
// 输出:value is NSNull
if let value: Any? = dict[aKey] {
    debugPrint("value is \(value)")
    // 输出:value is Optional(<null>)
    if let unwrappedValue = value {
        debugPrint("unwrappedValue is \(unwrappedValue)")
    } else {
        debugPrint("has no corresponding unwrappedValue")
    }
    // 输出:unwrappedValue is <null>
}    

使用 nil 的代码:

let aKey = "aKey"
var dict = [String: Any?]()
let value2: Any? = nil
dict[aKey] = value2
debugPrint(dict)
// 输出:["aKey": nil]
debugPrint(type(of: dict[aKey]))
// 输出:Swift.Optional<Swift.Optional<Any>>

if dict[aKey] is NSNull {
    debugPrint("value is NSNull")
} else {
    debugPrint("value is not NSNull")
}
// 输出:value is not NSNull

if let value: Any? = dict[aKey] {
    debugPrint("value is \(value)")
    // 输出:value is nil
    if let unwrappedValue = value {
        debugPrint("unwrappedValue is \(unwrappedValue)")
    } else {
        debugPrint("has no corresponding unwrappedValue")
    }
    // 输出:has no corresponding unwrappedValue
}

对比上面的两段代码,NSNull 和 Any? 并不是一致的。使用 NSNull 时,最终获取的时 NSNull 对象,使用 Any? 时,获取到的是 nil。

所以不推荐使用该方式,因为如果使用 NSNull,在最终判断是否有值时,还需要将存在的 NSNull 对象当做不存在(当做保存的为 nil)。

参考

How to add nil value to Swift Dictionary?