swift协议内幕

《Swift 进阶》协议部分 协议内幕中有这么一段代码

1
2
3
4
5
6
7
8
func f<C: CustomStringConvertible>(_ x: C) -> Int {
return MemoryLayout.size(ofValue: x)
}
func g(_ x: CustomStringConvertible) -> Int {
return MemoryLayout.size(ofValue: x)
}
f(5) // 8
g(5) // 40

方法 f 中 将协议 CustomStringConvertible 用作参数类型约束。在方法 g 中,将协议 CustomStringConvertible 用作参数类型。

对于同一个数字 5,打印出的参数 x 所占用的内存大小却有所差别。书中是这样解释的:

因为 f 接受的是泛型参数,整数 5 会被直接传递给这个函数,而不需要经过任何包装。所以它的大小是 8 字节,也就是 64 位系统中 Int 的尺寸。对于 g,整数会被封装到一个存在容器中。对于普通的协议 (也就是没有被约束为只能由 class 实现的协议),会使用不透明存在容器 (opaque existential container)。不透明存在容器中含有一个存储值的缓冲区 (大小为三个指针,也就是 24 字节);一些元数据 (一个指针,8 字节);以及若干个目击表 (0 个或者多个指针,每个 8 字节)。如果值无法放在缓冲区里,那么它将被存储到堆上,缓冲区里将变为存储引用,它将指向值在堆上的地址。元数据里包含关于类型的信息 (比如是否能够按条件进行类型转换等)

那么对于 g(5) 返回的 40 就知道是怎么来的了,

存储值的缓冲区(24)+ 元数据(8)+ 一个目击表(8)= 40

将上面的协议换一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Foo: Codable {
let name: String
let age: Int

init(name: String, age: Int) {
self.name = name
self.age = age
}

var description: String {
return "Foo(name: \(name), age: \(age))"
}
}

func f<C: Codable>(_ x: C) -> Int {
return MemoryLayout.size(ofValue: x)
}
func g(_ x: Codable) -> Int {
return MemoryLayout.size(ofValue: x)
}

let foo = Foo(name: "srv7", age: 18)

f(foo) // 24
g(foo) // 48

24 = String(16)+ Int(8)

48 = 存储值的缓冲区(24)+ 元数据(8)+ x * 目击表(8)=> x = 2
也就是说 CustomStringConvertible 换成 Codable 协议后,存在容器中的目击表数量为 2。

CustomStringConvertible 以一个简单协议,而 Codable 是一个组合协议

1
typealias Dodable = Decodable & Encodable

那么目击表的数量是不是根据协议数目来的呢?
将协议再换一下新增 typealias B = Codable & A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct Foo: Codable, A {
let name: String
let age: Int

init(name: String, age: Int) {
self.name = name
self.age = age
}

var description: String {
return "Foo(name: \(name), age: \(age))"
}
}

protocol A {

}

typealias B = Codable & A

func f<C: B>(_ x: C) -> Int {
return MemoryLayout.size(ofValue: x)
}
func g(_ x: B) -> Int {
return MemoryLayout.size(ofValue: x)
}

let foo = Foo(name: "srv7", age: 18)

f(foo) // 24
g(foo) // 56

56 = 存储值的缓冲区(24)+ 元数据(8)+ x * 目击表(8)=> x = 3

由上面这些代码可以猜测:存在容器中的目击表数量与协议数量一致

协议 协议数量 目击表数量
CustomStringConvertible 1 1
Codable 2 2
B 3 3

对于 g,整数会被封装到一个存在容器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// foo
sil_global hidden [let] @$s21existentialContainter3fooAA3FooVvp : $Foo
// 初始化 变量foo
alloc_global @$s21existentialContainter3fooAA3FooVvp // id: %2
%3 = global_addr @$s21existentialContainter3fooAA3FooVvp : $*Foo // users: %29, %16, %15
...
%14 = apply %13(%10, %12, %4) : $@convention(method) (@owned String, Int, @thin Foo.Type) -> @owned Foo // user: %15
store %14 to %3 : $*Foo

// 得到 foo 变量
%29 = load %3 : $*Foo // users: %32, %30
retain_value %29 : $Foo // id: %30
// 创建一个**存在**地址
%31 = init_existential_addr %28 : $*Decodable & Encodable, $Foo // user: %32
// 将 foo 存储到**存在**地址
store %29 to %31 : $*Foo