目录
- nil 的语义
- nil 在内存中的表示
- nil 的优化
- Bool
- String
- Class
- Enum
- 结语
nil 的语义
在 Objective-C 中,nil 表示空对象,它本质是一个指向 0x00000000 的指针。但对于非指针的值类型,OC 中是无法表示_没有值_这个概念的,比如 NSInteger,它可以是 0,也可以是其他任何值,但就是不存在_没有值_。
Swift 作为一种强类型的语言,它从一开始就引入了_没有值_这个概念,虽然还是用 nil 关键字,但实际语义上有所不同。比如 Int?,它可以是 nil,也可以是 0,0 是一个具体的值,而 nil 不是。然而,计算机作为一个二进制的机器,它内存中保存的非 0 即 1,如何表示_没有值_呢?换句话说,nil 在内存中究竟是什么?我们可以通过简单的代码找出它在内存中的真相。
nil 在内存中的表示
/// 以下方法取 value 的地址,并从地址处向后取它在内存中的大小 size 个字节,转为对应的数组
func bytes<T>(of value: T) -> [UInt8] {
var value = value
let size = MemoryLayout<T>.size
return withUnsafePointer(to: &value, {
$0.withMemoryRebound(
to: UInt8.self,
capacity: size,
{
Array(UnsafeBufferPointer(
start: $0, count: size))
})
})
}
var int: Int? = 0
bytes(of: int) // [0, 0, 0, 0, 0, 0, 0, 0, 0]
int = nil
bytes(of: int) // [0, 0, 0, 0, 0, 0, 0, 0, 1]
从上面我们可以得知,可选的 Int? 类型比普通 Int 类型多占一个字节,用来表示是不是 没有值。如果这样的话,在 struct 或 class 中用可选类型岂不是会浪费较多内存空间?因为内存对齐的缘故,多一个字节,就要浪费剩下的 7 字节,比如:
struct N {
var b: Int? = 2
var a: Int? = 3
}
var n = N()
bytes(of: n) // [2, 0, 0, 0, 0, 0, 0, 0, 0, 76, 68, 3, 1, 0, 0, 0,
// 3, 0, 0, 0, 0, 0, 0, 0, 0]
以上原本可以用 16 字节表示的结构体,实际上占了 25 字节(考虑结尾处内存对齐,其实占了 32 字节)。我们在实际开发中,可能会在 class 中声明大量的可选字段,如果都这样的话,那内存使用率也太低了,有优化手段吗?
答案是有的,而且 Swift 编译器已经默默帮我们做了。
nil 的优化
Bool
Bool 类型理论上只用 0 1 两个值,一个 bit 即可,但它却占了一整个 byte ,剩下的几个 bit 是可以用来区分是否有值的。
var b: Bool? = false bytes(of: b) // [0] b = true bytes(of: b) // [1] b = nil bytes(of: b) // [2]
从以上结果得知,Swift 用 2 表示 Bool? 的_没有值_,所以没有内存浪费。这样也使得 Bool? 不再是两态的开关,而是一个三态的开关。于是经常在代码中看到看起来比较蠢的写法:
var value: Bool?
if value == true {
}
因为一般来说是不建议 Bool 值与 true 判断等的,它本身已经是 Bool 了。而在 Swift 中又用起来是那么自然……
String
String 类型不同于 Int 这种——0 也是合法值,String 的内存值为 0 是可以表示_没有值_的,所以它也没有内存浪费

评论(0)