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