简介:
本文章主要介绍一下内容:
-
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类本质是HeapObjectHeapObject默认大小为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属性