「这是我参与2022初次更文挑战的第11天,活动概况查看:2022初次更文挑战」
iOS开发言语,不论是OC仍是Swift,都是经过LLVM进行编译的,终究生成.o文件,其编译流程如下图:
-
OC经过clang编译器,编译成IR,然后再生成可执行文件.o(也便是咱们的机器码); -
Swift则是经过Swift编译器生成IR,然后再生成可执行文件.o;
Swift编译流程
Swift言语编译流程图如下:
-
Swift代码经过-dump-parse命令进行语法剖析,生成笼统语法树AST; -
笼统语法树经过-dump-ast进行语义剖析(比方类型查看是否正确,是否安全); -
语义剖析之后,Swift代码将会降级为SIL,也便是Swift中心言语(Swift intermediate language); -
SIL分为Raw SIL(原生的,没有开启优化选项)和SILOpt Canonical SIL(经过优化的); - 终究经过
LLVM降级为IR,然后经过后段代码编译为不同架构的机器码;
流程中触及的命令如下:
// 剖析输出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
相关于OC,在Swift的编译进程中多了SIL,SIL会对咱们的代码进行安全查看,比方如下代码:
在OC中:
咱们定义的int8_t类型的计算成果将会出错,由于int8_t只有 一个字节,其运算成果现已溢出,在OC中给出了一个过错的运算成果;咱们再来看在Siwft中会产生什么:
咱们看到,同样的代码,在Swift中的编译阶段就会报错,防止后续产生不可预知的过错;这些都是由于SIL存在的成果;
SIL文件剖析
那么SIL究竟有着什么样的语法规矩呢?咱们来生成一个SIL文件进行剖析;咱们先写一段简略的代码:
接下来咱们将这段代码生成SIL文件:
为了方便观看,咱们也能够经过swiftc main.swift -emit-sil > ./main.sil指令将SIL文件输出为main.sil文件;
由于SIL文件内容较多,咱们只挑选重要的部分剖析:
Teacher的声明
首要,咱们先看一下Teacher在SIL阶段的声明部分:
从Teacher的定义中能够看到有初始化的存储属性age和name,还有一个标识为@objc的deinit函数,以及默许的初始化器init();
main函数
-
@main:标识入口函数,在SIL中@是作为标识符的; -
%0—%9:这写在SIL中也被称为寄存器,能够理解为开发进程中的常量,一旦赋值,将无法再次修改(所以后边数字会一向累加);需求注意的是,此处的寄存器是虚拟寄存器,终究运转到详细的设备上时,会运用真实的寄存器; -
alloc_global @$s4main1tAA7TeacherCvp:分配一个全局变量,该全局变量的名称是混写的,咱们能够经过终端指令xcrun swift-demangle将其还原:
能够看到此处对应的是main文件中的t变量,对应的是main中的Teacher;
-
%3 = global_addr @$s4main1tAA7TeacherCvp:拿到该全局变量的地址给%3; -
%4 = metatype $@thick Teacher.Type:获取Teacher.Type的元类型给%4; - 依据注释能够知道
%5是Teacher.__allocating_init()函数的引证,也便是其指针地址; -
%6 = apply %5(%4):将%5(%4)的成果,也便是Teacher的实例变量,赋值给%6; -
store %6 to %3:将%6也便是实例变量的内存地址,存放到%3这个全局变量中; - 在
Swift底层中Int便是一个Struct类型,%8和%9是在构建一个Int32的整数类型0,类似与咱们OC中main函数终究的return 0;
关于SIL语法规矩,请查看SIL官方文档
__allocating_init()函数
在上述SIL代码中调用了s4main7TeacherCACycfC也便是Teacher.__allocating_init()函数来创立当前的实例对象的;咱们在SIL中定位到该函数:
- 该函数需求一个
Teacher.Type的元类型,咱们能够将此元类型理解为isa; -
%1 = alloc_ref $Teacher:alloc_ref会创立一个Teacher的实例变量,其引证计数初始化为1;alloc_ref实际上也便是去堆区请求内存空间;假如标识为objc的Swift类将会运用Objective-C的+allocWithZone:初始化办法;怎么验证呢?咱们在代码中增加如下断点:
运转程序,查看汇编指令:
咱们看到将会调用Teacher.__allocating_init()函数,那么该函数是怎么完成的呢?断点进入该函数:
在__allocating_init()函数中主要调用了swift_allocObject和Teacher.init();
-
swift_allocObject在堆区找到合适的内存空间初始化; -
Teacher.init()初始化成员变量;
这是一个朴实的Swift类,那么假如咱们将Teacher继承自NSObject会产生什么呢?
咱们重复上述汇编调试步骤,进入Teacher.__allocating_init()函数:
咱们发现,初始化函数变成了objc_allocWithZone以及objc_msgSend;
-
objc_allocWithZone调用malloc函数请求内存空间; -
objc_msgSend发送init音讯;
swift_allocObject
在前边咱们现已剖分出Swift类初始化进程中会调用swift_allocObject,那么该函数做了什么呢?咱们需求借助于Swift源码进行剖析;
在该目录下找到stdlib->public->runtime->HeapObject.cpp文件,该文件是和咱们的Swift类初始化相关的文件;在该文件中定位到_swift_allocObject_函数,这是一个私有函数,其是被swift::swift_allocObject调用起来的:
_swift_allocObject_完成如下:
该函数有三个参数:
-
HdapMetadata const *metadata:元数据类型; -
requiredSize:所需求的巨细; -
requiredAlignmentMask:对齐所需求的掩码,能够从objc的源码中得知,其为7,由于是8字节对齐;
将requiredSize和requiredAlignmentMask传递给函数swift_slowAlloc,该函数返回了一个HeapObject类型的指针; reinterpret_cast用来做指针类型的转化;
-
new (object) HeapObject(metadata):HeapObject初始化;
那么swift_slowAlloc是用来干什么的呢?
swift_slowAlloc
其完成如下:
在swift_slowAlloc函数中,调用了malloc函数来开辟内存空间;
流程总结
那么,咱们大致能够总结出Swift对象进行内存分配的流程:
- 首要会调用
_allocating_init():该函数有编译器生成; - 关于纯
Swift类将会再调用swift_allocObject()函数; - 然后在
swift_allocObjec()总会调用私有函数_swift_allocObject; - 然后经过函数
swift_slowAlloc调用malloc来请求堆区的内存空间;



















