本文主要介绍以下几点,文章最后会总结。
-
通过SIL来理解对象的创建
-
Swift类结构分析
-
存储属性 & 计算属性
-
延迟存储属性 & 单例创建方式
SIL
在底层流程中,OC代码和SWift代码时通过不同的编译器
进行编译,然后通过LLVM
,生成.o
可执行文件,如下所示
SIL-1
-
OC
中通过clang
编译器(clang可以参考这篇文章iOS-底层原理 31:LLVM编译流程 & Clang插件开发),编译成IR,然后再生成可执行文件.o(即机器码) -
swift
中通过swiftc
编译器,编译成IR,然后再生成可执行文件
下面是Swift中的编译流程,其中SIL
(Swift Intermediate Language),是Swift编译过程中的中间代码
,主要用于进一步分析和优化Swift代码。如下图所示,SIL
位于在AST
和LLVM
IR之间
SIL-2
注意:这里需要说明一下,Swift与OC的区别
在于 Swift生成了高级的SIL
我们可以通过swiftc -h
终端命令,查看swiftc的所有命令
SIL-3
例如:在main.swift文件定义如下代码
class CJLTeacher{ var age: Int = 18 var name: String = "CJL"}var t = CJLTeacher()
-
查看抽象语法树:
swiftc -dump-ast main.swift
SIL-4
* 生成SIL文件:`swiftc -emit-sil main.swift >> ./main.sil && code main.sil`,其中main的入口函数如下
// main
//`@main`:标识当前main.swift的`入口函数`,SIL中的标识符名称以`@`作为前缀
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
//`%0、%1` 在SIL中叫做寄存器,可以理解为开发中的常量,一旦赋值就不可修改,如果还想继续使用,就需要不断的累加数字(注意:这里的寄存器,与`register read`中的寄存器是有所区别的,这里是指`虚拟寄存器`,而`register read`中是`真寄存器`)
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
//`alloc_global`:创建一个`全局变量`,即代码中的`t`
alloc_global @$s4main1tAA10CJLTeacherCvp // id: %2
//`global_addr`:获取全局变量地址,并赋值给寄存器%3
%3 = global_addr @$s4main1tAA10CJLTeacherCvp : $*CJLTeacher // user: %7
//`metatype`获取`CJLTeacher`的`MetaData`赋值给%4
%4 = metatype $@thick CJLTeacher.Type // user: %6
//将`__allocating_init`的函数地址赋值给 %5
// function_ref CJLTeacher.__allocating_init()
%5 = function_ref @$s4main10CJLTeacherCACycfC : $@convention(method) (@thick CJLTeacher.Type) -> @owned CJLTeacher // user: %6
//`apply`调用 `__allocating_init` 初始化一个变量,赋值给%6
%6 = apply %5(%4) : $@convention(method) (@thick CJLTeacher.Type) -> @owned CJLTeacher // user: %7
//将%6的值存储到%3,即全局变量的地址(这里与前面的%3形成一个闭环)
store %6 to %3 : $*CJLTeacher // id: %7
//构建`Int`,并`return`
%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'
注意:code
命令是在.zshrc
中做了如下配置,可以在终端中指定软件打开相应文件
$ open .zshrc
//****** 添加以下别名
alias subl='/Applications/SublimeText.app/Contents/SharedSupport/bin/subl'
alias code='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code'
//****** 使用
$ code main.sil
//如果想SIL文件高亮,需要安装插件:VSCode SIL
-
从SIL文件中,可以看出,代码是经过混淆的,可以通过以下命令还原,以
s4main1tAA10CJLTeacherCvp
为例:xcrun swift-demangle s4main1tAA10CJLTeacherCvp
SIL-5
-
在SIL文件中搜索
s4main10CJLTeacherCACycfC
,其内部实现主要是分配内存+初始化变量
-
allocing_ref
:创建一个CJLTeacher
的实例对象,当前实例对象的引用计数为1
-
调用
init
方法
//********* main入口函数中代码 *********
%5 = function_ref @$s4main10CJLTeacherCACycfC : $@convention(method) (@thick CJLTeacher.Type) -> @owned CJLTeacher
// s4main10CJLTeacherCACycfC 实际就是__allocating_init()
// CJLTeacher.__allocating_init()
sil hidden [exact_self_class] @$s4main10CJLTeacherCACycfC : $@convention(method) (@thick CJLTeacher.Type) -> @owned CJLTeacher {
// %0 "$metatype"
bb0(%0 : $@thick CJLTeacher.Type):
// 堆上分配内存空间
%1 = alloc_ref $CJLTeacher // user: %3
// function_ref CJLTeacher.init() 初始化当前变量
%2 = function_ref @$s4main10CJLTeacherCACycfc : $@convention(method) (@owned CJLTeacher) -> @owned CJLTeacher // user: %3
// 返回
%3 = apply %2(%1) : $@convention(method) (@owned CJLTeacher) -> @owned CJLTeacher // user: %4
return %3 : $CJLTeacher // id: %4
} // end sil function '$s4main10CJLTeacherCACycfC'
SIL语言对于Swift源码的分析是非常重要的,关于其更多的语法信息,可以在这个网站进行查询
符号断点调试
-
在demo中设置
_allocing_init
符号断点SIL-6
发现其内部调用的是
swift_allocObject
SIL-7
源码调试
下面我们就通过swift_allocObject
来探索swift中对象的创建过程
-
在
REPL
(命令交互行,类似于python的,可以在这里编写代码)中编写如下代码(也可以拷贝),并搜索swift_allocObject
函数加一个断点,然后定义一个实例对象tSIL-8
-
断点断住,查看左边local有详细的信息
SIL-9
-
其中
requiredSize
是分配的实际内存大小,为40 -
requiredAlignmentMask
是swift中的字节对齐方式,这个和OC中是一样的,必须是8
的倍数,不足的会自动补齐,目的是以空间换时间
,来提高内存操作效率
swift_allocObject 源码分析
swift_allocObject
的源码如下,主要有以下几部分
-
通过
swift_slowAlloc
分配内存