3. 类的初始化

146 阅读3分钟

一、编译流程

be7778896dab34911e8169e6ff1db295.png iOS的开发语言不管是OC还是Swift最终都是通过LLVM进行编译

  1. OC通过 clang 编译器编译成IR,然后再生成可执行文件.o(机器码)
  2. Swift 则是通过 Swift 编译器编译成 IR,然后再生成可执行文件。

65dfbd52a0042fdaf0f0b62c9be29d5a.png

  • Swift代码经过-dump-parse命令进行语法分析,生成抽象语法树AST
  • 抽象语法树通过-dump-ast进行语义分析(比如类型检查是否正确,是否安全);
  • 语义分析之后,Swift代码将会降级为SIL,也就是Swift中间语言(Swift intermediate language);
  • SIL分为Raw SIL(原生的,没有开启优化选项)和SILOptimizer Canonical SIL(经过优化的);
  • 最终通过LLVM降级为IR,然后通过后端编译为不同架构的Machine Code(机器码)

附:常用命令

// 分析输出AST
swiftc main.swift -dump-parse
// 分析并且检查类型输出AST
swiftc main.swift -dump-ast
// 生成中间体语言(SIL),未优化
swiftc main.swift -emit-silgen
// 生成中间体语言(SIL),优化后的
swiftc main.swift -emit-sil
// 生成LLVM中间体语言 (.ll文件)
swiftc main.swift -emit-ir
// 生成LLVM中间体语言 (.bc文件)
swiftc main.swift -emit-bc
// 生成汇编
swiftc main.swift -emit-assembly
// 编译生成可执行.out文件
swiftc -o main.o main.swift

二、通过SIL(Swift Intermediate Language )分析

  1. 测试代码
class Teacher {
    var name = "Tom"
    var age = 3
}

let t = Teacher()

这里为了方便我们直接在工程中配置命令 7d4127b9474abcf02ca30c3df70cb517.png

swiftc -emit-silgen -Onone ${SRCROOT}/TestDemo/main.swift > ./main.sil && open main.sil
  1. main.sil
  • main函数
// Teacher类有2个已初始化的属性
// 包含 构造方法 和 析构方法
class Teacher {
  @_hasStorage @_hasInitialValue var name: String { get set }
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}

@_hasStorage @_hasInitialValue let t: Teacher { get }

// t
sil_global hidden [let] @$s4main1tAA7TeacherCvp : $Teacher

// main 入口函数
// %1 %2 %3... 为虚拟寄存器
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  // 分配一个全局变量,s4main1tAA7TeacherCvp为混淆后的名称,可通过 xcrun swift-demangle s4main1tAA7TeacherCvp 还原成 main.t : main.Teacher
  alloc_global @$s4main1tAA7TeacherCvp            // id: %2
  
  // 申请全量变量内存地址给寄存器 %3
  %3 = global_addr @$s4main1tAA7TeacherCvp : $*Teacher // user: %7
  
  // 将 Teacher.Type 的元类型给寄存器 %4
  %4 = metatype $@thick Teacher.Type              // user: %6
  
  // function_ref Teacher.__allocating_init()
  // 将 Teacher.__allocating_init() 的函数地址给寄存器 %5
  %5 = function_ref @$s4main7TeacherCACycfC : $@convention(method) (@thick Teacher.Type) -> @owned Teacher // user: %6
  
  // 调用 %5 的函数并携带一个参数 %4(元类型),将结果给寄存器 %6
  %6 = apply %5(%4) : $@convention(method) (@thick Teacher.Type) -> @owned Teacher // user: %7
  
  // 将 %6(Teacher实例内存地址) 存储到全局变量 %3
  store %6 to [init] %3 : $*Teacher               // id: %7
  
  // %8、%9、return %9:构建一个Int32位的整数类型0(Swift中Int为结构体)并返回(main函数结束)
  %8 = integer_literal $Builtin.Int32, 0          // user: %9
  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
  return %9 : $Int32                              // id: %10
} // end sil function 'main'
  • Teacher的构造函数 Teacher.__allocating_init()
// Teacher.__allocating_init()
sil hidden [exact_self_class] [ossa] @$s4main7TeacherCACycfC : $@convention(method) (@thick Teacher.Type) -> @owned Teacher {
// %0 "$metatype" 元类型(isa)
bb0(%0 : $@thick Teacher.Type):】
  // 在堆中申请内存并返给 %1
  %1 = alloc_ref $Teacher                         // user: %3
  
  // function_ref Teacher.init()
  // 获取函数地址返回 %2
  %2 = function_ref @$s4main7TeacherCACycfc : $@convention(method) (@owned Teacher) -> @owned Teacher // user: %3
  
  // 调用 %2 的函数并传参 %1 结果返给 %3
  %3 = apply %2(%1) : $@convention(method) (@owned Teacher) -> @owned Teacher // user: %4
  
  // 返回 %3 
  return %3 : $Teacher                            // id: %4
} // end sil function '$s4main7TeacherCACycfC'

三、通过汇编分析

  • 分析 Teacher.__allocating_init()

83af41faba3569010f74abb67d55d210.png 在测试代码中断点并找到 Teacher.__allocating_init,此处按住 control 再到下一步查看详情。

3f69cd753e353fa33cd78b9767f3257d.png 此处可发现 Swift 类初始化调用 swift_allocObject 分配了堆内存,之后调用了 init 方法。

注意:若 Teacher 是 NSObject 子类则初始化方式有所区别。 f7d28e2dcd2b895834113324fbf67e6a.png 很明显,走是 OC 的 objc_allocWithZone 和 objc_msgSend。

四、通过 Swift 源码分析

  • swift_allocObject 的调用( HeapObject.cpp 文件) 586b3640f59aca3b130726bbc41f6c09.png 由此可以看到最终调到 swift_allocObject 方法中。
/// 调用 swift_slowAlloc 并传参,返回值为 HeapObject 类型的指针
auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));
/// 调用 HeapObject 的初始化方法并将 metadata 传入,最后将结果返回
new (object) HeapObject(metadata);
  • swift_slowAlloc 的调用(Heap.cpp 文件中) 3012444a0e48eba99b054f0157862396.png 很明显此处在 malloc,从堆上申请内存。

五、总结

到此我们大致可以总结出类的初始化流程:

  1. 首先会调用__allocating_init:该函数由编译器生成(默认构造方法);
  2. 对于纯Swift类将会再调用swift_allocObject函数;
  3. 然后swift_allocObjec最终会调用私有函数_swift_allocObject_
  4. 然后通过函数swift_slowAlloc调用malloc来申请堆区的内存空间;