Swift类、对象、属性

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文件的编译过程要经过哪些步骤,如下图:

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拿到PersonMetadata赋值给%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?
    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()

上面的nameage都是存储属性,在SIL可以验证这点,如下图:

  • 计算属性(就是不占用存储空间的属性,它的本质是getset方法)
  • 属性观察者(willSetdidSet
  • 延迟存储属性(延迟存储属性的值在第一次使用的时候才进行赋值。用关键字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


(lldb) po p.age
26

你可能感兴趣的:(Swift类、对象、属性)