Swift-02:Swift编译&类

1,157 阅读5分钟

Swift 进阶之路 文章汇总

简介:

本文章主要介绍一下内容:

  • 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的初始化

  1. 其中metadata类型是HeapMetadata,是一个指针类型,占8字节

  2. refCounts(引用计数,类型是InlineRefCounts,而InlineRefCounts是一个类RefCounts的别名,占8个字节),swift采用arc引用计数

  3. 此时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结构:

refCountInlineRefCounts类型,进入探究:InlineRefCounts->RefCounts,是class类型,占8字节swift也使用ARC进行内存管理

进入TargetHeapMetaData定义,其本质是一个模板类型,其中定义了一些所需的数据结构。这个结构体中没有属性,只有初始化方法,传入了一个MetadataKind类型的参数(该结构体没有,那么只有在父类中了)这里的kind就是传入的Inprocess

由此可见:

  1. swift类本质是HeapObject
  2. HeapObject默认大小为16字节metadata(struct)8字节refCounts(class)8字节
  3. LGPersonage(Int)占8字节name(String)占16字节

继续查看TargetMetadata结构体查看getClassObject

查找getClassObject实现

综上所述,当metadatakind为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属性