简介:
本文章主要介绍一下内容:
-
SIL分析
-
类的结果探索
Swift编译简介
用Swift语言创建一段简单的类,并默认实例化
class LGPerson {
var age: Int = 18
var name: String = "World"
}
let t = LGPerson()
需要研究的是,这个实例化到底做了一个什么样的操作?因此引入了SIL (Swift intermediate language
) ,再来阅读 SIL 的代码之前,我们先来了解⼀下什么是 SIL
。
iOS开发的语⾔不管是 OC
还是 Swift
后端
都是通过 LLVM
进⾏编译
的,如下图所示:
可以看到:
OC
通过 clang 编译器
,编译成IR
,然后再⽣成可执⾏⽂件.o
(这⾥也就是我们的机器码)
Swift
则是通过 Swift编译器
编译成IR
,然后在⽣成可执⾏⽂件
。
我们再来看⼀下,⼀个 swift
⽂件的编译过程
都经历了哪些步骤:
编译流程可参考:《WWDC - 2015 LLVM视频》
SIL分析
SIL常用语法命令
-dump-ast 语法和类型检查,打印AST语法树
-dump-parse 语法检查,打印AST语法树
-dump-pcm 转储有关预编译Clang模块的调试信息
-dump-scope-maps <expanded-or-list-of-line:column>
Parse and type-check input file(s) and dump the scope map(s)
-dump-type-info Output YAML dump of fixed-size types from all imported modules
-dump-type-refinement-contexts
Type-check input file(s) and dump type refinement contexts(s)
-emit-assembly Emit assembly file(s) (-S)
-emit-bc 输出一个LLVM的BC文件
-emit-executable 输出一个可执行文件
-emit-imported-modules 展示导入的模块列表
-emit-ir 展示IR中间代码
-emit-library 输出一个dylib动态库
-emit-object 输出一个.o机器文件
-emit-pcm Emit a precompiled Clang module from a module map
-emit-sibgen 输出一个.sib的原始SIL文件
-emit-sib 输出一个.sib的标准SIL文件
-emit-silgen 展示原始SIL文件
-emit-sil 展示标准的SIL文件
-index-file 为源文件生成索引数据
-parse 解析文件
-print-ast 解析文件并打印(漂亮/简洁的)语法树
-resolve-imports 解析import导入的文件
-typecheck 检查文件类型
SIL语法命令可参考:《SIL文档》
用SIL命令
完成刚才创建的Swift测试用
例:
打开终端命令cd到工程的根目录下:
- 查看抽象语法树:
swiftc -dump-ast main.swift
- 生成SIL文件:
swiftc -emit-sil main.swift >> ./main.sil
,其中main的入口函数如下在根目录下找到编译好的.sil
文件,用VSCode
打开
- 从
SIL文件
中,可以看出,代码是经过混淆
的,可以通过以下命令还原,以s4main1tAA8LGPersonCvp
为例在
终端输入:xcrun swift-demangle s4main1tAA8LGPersonCvp
s4main1tAA8LGPersonCvp
代表的是main.LGPerson
类
搜索s4main1tAA8LGPersonCvp
查找根据main函数
中传的@thick参数
查找函数方法
- 断点调试
打开xcode,添加_allocating_init
断点,使用汇编方式调试,可以看出一下结论
Swift内存分配
过程中发⽣的事情, __allocating_init
----->swift_allocObject
-----> _ swift_allocObject_
-----> swift_slowAlloc
----->Malloc
源码调试
swift_allocObject断点调试
上篇文章
中有提到swift_allocObject
方法的来源问题,这里有直接体现,
在终端调试
中编写如下代码(也可以拷贝),并搜索swift_allocObject
函数加一个断点,然后定义一个实例对象
_swift_allocObject_ 源码分析
打开_swift_allocObject_
源码
- swift_slowAlloc源码分析
注意:
swift_slowAlloc
方法上有系统的注释说明,请留意查看
- HeapObject源码解析
查找HeapObject
源码,进入HeapObject.h文件中,查找HeapObject
的初始化
-
其中
metadata
类型是HeapMetadata
,是一个指针类型,占8
字节 -
refCounts
(引用计数,类型是InlineRefCounts
,而InlineRefCounts
是一个类RefCounts
的别名,占8
个字节),swift采用arc引用计数 -
此时
object
是强转的HeapObject
类型,实际是一个指向内存空间
的对象指针
,而HeapObject
需要使用metadata
进行初始化
总结:
- swift类的创建过程:
-
查看内存大小
//********* Int底层定义 ********* @frozen public struct Int : FixedWidthInteger, SignedInteger {...}
//********* String底层定义 ********* @frozen public struct String {...}
//********* 验证 ********* print(MemoryLayout.stride) print(MemoryLayout.stride)
//********* 打印结果 ********* 8 16
-
总结
从打印的结果中可以看出,Int
类型占8
字节,String
类型占16
字节(后面文章会进行详细讲解),这点与OC中是有所区别的
所以这也解释了为什么LGPerson
的内存大小等于40
,即40 = metadata(8字节) +refCount(8字节)+ Int(8字节)+ String(16字节)
探索Swift类的组成
进入HeapObject
结构:
refCount
是InlineRefCounts
类型,进入探究:InlineRefCounts
->RefCounts
,是class
类型,占8字节
。swift
也使用ARC
进行内存管理
进入TargetHeapMetaData
定义,其本质是一个模板类型
,其中定义了一些所需的数据结构。这个结构体中没有属性,只有初始化
方法,传入了一个MetadataKind
类型的参数(该结构体没有,那么只有在父类中了)这里的kind
就是传入的Inprocess
由此可见:
swift类
本质是HeapObject
HeapObject
默认大小为16字节
:metadata
(struct)8字节
和refCounts
(class)8字节
LGPerson
的age
(Int)占8字节
,name
(String)占16字节
继续查看TargetMetadata
结构体查看getClassObject
查找getClassObject
实现
综上所述,当metadata
的kind
为Class时,有如下继承链
前Class在内存中所存放的属性
由 TargetClassMetadata
属性 + TargetAnyClassMetaData
属性 + TargetMetaData
属性 构成,所以得出的metadata的数据结构体如下所示
struct swift_class_t: NSObject{
void *kind;//相当于OC中的isa,kind的实际类型是unsigned long
void *superClass;
void *cacheData;
void *data;
uint32_t flags; //4字节
uint32_t instanceAddressOffset;//4字节
uint32_t instanceSize;//4字节
uint16_t instanceAlignMask;//2字节
uint16_t reserved;//2字节
uint32_t classSize;//4字节
uint32_t classAddressOffset;//4字节
void *description;
...
}
至此类的结构
分析完成,下一章分析类的属性
&属性观察者
以及lazy属性