1.Swift编译简介
2.SIL分析
3.类结构探索
4.Swift属性
Swift编译简介
class Person {
var name = "dotry"
var age = 26
}
let p = Person()
我们要研究的是这个默认的初始化器到底做了什么,这里我们引入SIL(Swift Intermediate Language)
。先将Swift代码编译成SIL再来阅读分析。在这之前我们先要了解什么是SIL。
当我们进行iOS开发的时候,不管选择的语言是OC
还是Swift
,后端都是用LLVM
编译的。OC
通过clang编译器编
译成IR,Swift
通过Swift编译器
编译成IR,然后在生成可执行文件.o(机器码)。如下图:
Swift
在编译的时候,前端用到的是swiftc
,在OC
中用到的是clang
。在终端使用swiftc -h
命令可以查看swiftc
的功能。
如果想要对SIL进行更详细的探索,可以观看这个视频。
SIL分析
SIL参考文档
先将上面的代码通过swiftc
编译成SIL
。
swiftc -emit-sil main.swift|xcrun swift-demangle > main.sil
因为编译之后的符号是处理过的符号,不方便查看,通过xcrun swift-demangle
可以还原符号,如下图:
- @main标识当前man.swift的入口函数,SIL中的 标识符名称以@作为前缀。
- %0,%1……在SIL中也叫做寄存器,可以理解为平时代码中定义的常量,一旦赋值之后就不可以改变。如果SIL中还要继续使用,只有不断地累加数字。同时这里的寄存器是虚拟的,最终运行到设备上使用的是真正的寄存器。
-
alloc_global
创建一个全局变量。 -
global_addr
拿到全局变量的地址,赋值给%3。 -
metatype
拿到Person
的Metadata
赋值给%4。 - 将
__allocating_init
的函数地址赋值给%5。 -
apply
调用__allocating_init
,并把返回值赋值给%6。 - 将%6赋值给刚才创建的全局变量%3。
- 构建
Int
,并return
。
Person
的初始化,如下图:
-
alloc_ref
:创建一个Person的实例对象,引用计数默认为1。 - 调用
init
方法。
通过断点,发现内部调用了swift_allocObject
。然后再通过VSCode
调试看到了swift_allocObject
内部又调用了什么,如下图:
以上我们得出了一个结论:
- Swift内存分配发生的事情,
__allocating_init
-->swift_allocObject
-->_swift_allocObject_
-->swift_slowAlloc
-->malloc
。 - Swift对象的内存结构
HeapObject
有两个属性,一个是Metadata
,一个是RefCount
。所以一个Swift对象至少占用16个字节。 -
init
在这里和OC中一样,承担了初始化内存的职责。
类结构探索
看了上面的内存分配之后,我们看到了一个Metadata
。它是一个HeapMetadata
类型,看一下它的内存结构,其中kind
的种类是一个enum
,由一个文件定义的。如下图:
最终kind
应该是下图所示:
再来看看Metadata
的内存结构,如下图:
所以Swift的类结构应该如下所示:
struct Metadata {
// 来自TargetMetadata
var kind: UnsafeRawPointer
// 来自TargetAnyClassMetadata
var superClass: UnsafePointer<Metadata>?
var cachedata1: UnsafeRawPointer
var cachedata2: UnsafeRawPointer
var data: UnsafeRawPointer
// 来自TargetClassMetadata
var flags: UInt32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var description: UnsafeRawPointer
var iVarDestroyer: UnsafeRawPointer?
}
引用计数的探究
通过查看源码发现refcount
的内部结构如下图所示。
static const size_t PureSwiftDeallocShift = 0;
static const size_t PureSwiftDeallocBitCount = 1;
static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);
static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
static const size_t UnownedRefCountBitCount = 31;
static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);
static const size_t IsImmortalShift = 0; // overlaps PureSwiftDealloc and UnownedRefCount
static const size_t IsImmortalBitCount = 32;
static const uint64_t IsImmortalMask = maskForField(IsImmortal);
static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
static const size_t IsDeinitingBitCount = 1;
static const uint64_t IsDeinitingMask = maskForField(IsDeiniting);
static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
static const size_t StrongExtraRefCountBitCount = 30;
static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
static const size_t UseSlowRCBitCount = 1;
static const uint64_t UseSlowRCMask = maskForField(UseSlowRC);
static const size_t SideTableShift = 0;
static const size_t SideTableBitCount = 62;
static const uint64_t SideTableMask = maskForField(SideTable);
static const size_t SideTableUnusedLowBits = 3;
static const size_t SideTableMarkShift = SideTableBitCount;
static const size_t SideTableMarkBitCount = 1;
static const uint64_t SideTableMarkMask = maskForField(SideTableMark);
不同的位有不同的含义:
当对象没有被弱引用
时,就按照上图所示保存对应的信息。一旦被弱引用
后就会生成一张表,此时保存的是这样表的地址。然后通过表管理对象的引用计数。
写个例子验证一下:
class Person {
var age = 26
var sex = 1
var name = "dotry"
}
let person = Person()
let fun = { [unowned person] in
print(person)
}
let person1 = person
weak var person2 = person
(lldb) v -L person
scalar: (TestSwift.Person) person = 0x00000002831a40f0 {
0x00000002831a4100: age = 26
0x00000002831a4108: sex = 1
0x00000002831a4110: name = "dotry"
}
// 初始化
(lldb) x/2g 0x00000002831a40f0
0x2831a40f0: 0x00000001002e9f98 0x0000000000000002
// 捕获列表无主引用
(lldb) x/2g 0x00000002831a40f0
0x2831a40f0: 0x00000001002e9f98 0x0000000000000004
// 强引用
(lldb) x/2g 0x00000002831a40f0
0x2831a40f0: 0x00000001002e9f98 0x0000000200000004
// 弱引用
(lldb) x/2g 0x00000002831a40f0
0x2831a40f0: 0x00000001002e9f98 0xc0000000507fb800
(lldb) p/x 0x507fb800 << 3
(Int) $R12 = 0x0000000283fdc000
(lldb) x/4g 0x0000000283fdc000
0x283fdc000: 0x00000002831a40f0 0x0000000100000003
0x283fdc010: 0x0000000200000004 0x0000000000000002
Swift属性
- 存储属性(可以是
let
修饰,也可以是var
修饰)
class Person {
var name = "dotry"
var age = 26
}
let p = Person()
上面的name
和age
都是存储属性,在SIL
可以验证这点,如下图:
- 计算属性(就是不占用存储空间的属性,它的本质是
get
和set
方法) - 属性观察者(
willSet
和didSet
) - 延迟存储属性(延迟存储属性的值在第一次使用的时候才进行赋值。用关键字
lazy
来修饰一个延迟存储属性。) - 类型属性(不管当前的类型有多少个实例,类型属性都是唯一的。用
static
来修饰一个类型属性。) - 单例
class Person {
var name = "dotry"
var age = 26
static let shared = Person()
private init() {}
}
let person = Person.shared
- 值类型(存储的就是属性的值,如
age
。) - 引用类型 (存储的是值在内存中的地址,如
person
。)
(lldb) po p
<Person: 0x100695a40>
(lldb) po p.age
26