本文作者:苯酚
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 是能够示意_没有值_的,所以它也没有内存节约
var s: String? = "abc"
bytes(of: s) // [97, 98, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 227]
s = ""
bytes(of: s) // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224]
s = nil
bytes(of: s) // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
String 在 Swift 中是一个构造体,无论字符串多长,String 变量自身只占 16 字节,短的字符串通过相似 OC 中 Tagged Pointer 的技术间接存在指针中,长的字符串须要指向堆内存地址。
Class
Class 类型同 OC 中的一样,是指针类型,空指针能够示意_没有值_,没有内存节约。
class MyObject {
var b: Int? = 2
var a: Int? = 3
}
var o: MyObject? = .init()
bytes(of: o) // [160, 142, 188, 2, 0, 96, 0, 0]
o = nil
bytes(of: o) // [0, 0, 0, 0, 0, 0, 0, 0]
无论 Class 中有多少成员变量,Class 变量自身(即指向它的指针)只占 8 字节(64 位零碎中)。
Enum
枚举类型个别是无限的,最终总能够找到一个不在枚举范畴内的值示意 _没有值_,也能够没有内存节约。
enum Edge {
case left
case right
case top
case bottom
}
var e: Edge? = .left
bytes(of: e) // [0]
e = .bottom
bytes(of: e) // [3]
e = nil
bytes(of: e) // [4],用越界值示意 nil,没有值
当然并不是所有 Enum 类型都能这样,带关联值的就可能不行。
结语
综上所述,Swift 编译器会尽可能地优化可选值的内存占用,日常开发并不需要太多关怀,然而局部状况仍要求开发者尽量少应用可选值,如构造体中间断几个可选 Int 的状况,如果 0 也能满足代码逻辑,就应用非可选值,并用 0 初始化它吧!
// 节约的内存比拟可观
struct My {
var a: Int?
var b: Int?
var c: Int?
var d: Int?
}
本文公布自网易云音乐技术团队,文章未经受权禁止任何模式的转载。咱们长年招收各类技术岗位,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe(at)corp.netease.com!