一、编译流程
iOS的开发语言不管是OC还是Swift最终都是通过LLVM进行编译
- OC通过 clang 编译器编译成IR,然后再生成可执行文件.o(机器码)
- Swift 则是通过 Swift 编译器编译成 IR,然后再生成可执行文件。
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 )分析
- 测试代码
class Teacher {
var name = "Tom"
var age = 3
}
let t = Teacher()
这里为了方便我们直接在工程中配置命令
swiftc -emit-silgen -Onone ${SRCROOT}/TestDemo/main.swift > ./main.sil && open main.sil
- 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()
在测试代码中断点并找到 Teacher.__allocating_init,此处按住 control 再到下一步查看详情。
此处可发现 Swift 类初始化调用 swift_allocObject 分配了堆内存,之后调用了 init 方法。
注意:若 Teacher 是 NSObject 子类则初始化方式有所区别。
很明显,走是 OC 的 objc_allocWithZone 和 objc_msgSend。
四、通过 Swift 源码分析
- swift_allocObject 的调用( HeapObject.cpp 文件)
由此可以看到最终调到 swift_allocObject 方法中。
/// 调用 swift_slowAlloc 并传参,返回值为 HeapObject 类型的指针
auto object = reinterpret_cast<HeapObject *>(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
/// 调用 HeapObject 的初始化方法并将 metadata 传入,最后将结果返回
new (object) HeapObject(metadata);
- swift_slowAlloc 的调用(Heap.cpp 文件中)
很明显此处在 malloc,从堆上申请内存。
五、总结
到此我们大致可以总结出类的初始化流程:
- 首先会调用
__allocating_init:该函数由编译器生成(默认构造方法); - 对于纯
Swift类将会再调用swift_allocObject函数; - 然后
swift_allocObjec最终会调用私有函数_swift_allocObject_; - 然后通过函数
swift_slowAlloc调用malloc来申请堆区的内存空间;