这是我参与更文挑战的第18天,活动详情查看: 更文挑战
下面是我在阅读 [SwiftyJSON] 源码过程中的收获。
其他文章
字面量转换
在 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)。