「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
- 本文主要介绍类的生命周期分析
我们知道在iOS中,app开发通过编译器
把我们的代码编译成机器可识别的代码mach-o
文件,编译器分为前端编译器
和后端编译器
,oc中前端编译器为clang
,swift中为swift编译器
。通过前端编译器 进行词法分析
,语法分析
,检查语法
是否正确生成中间代码IR
之后通过中间层进行代码优化
,交给后端llvm
进行处理,生成mach-o
文件
流程如下:
具体流程可以参考我之前的文章llvm流程
我们接下来看下前端编译器swift的命令
,类似我们oc中clang的命令语句
// 分析输出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
复制代码
1. SIL文件分析
根据上面的指令我们生成main的sil文件,我们新建一个CommandLineTool
工程,在main定义一个Person类
在终端输入下面的代码会生成sil
文件,并在终端打印生成的main.sil
文件
swiftc main.swift -emit-sil
复制代码
上面的 person就是我们的类,包含属性的get
和set
方法,以及deinit
和init
方法。@main是入口函数。
我们可以通过> 在当前目录下生成main.sil
文件
swiftc main.swift -emit-sil > ./main.sil
复制代码
// main 相当于oc中main的入口
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1pAA6PersonCvp // id: %2 分配一个person的全局变量
%3 = global_addr @$s4main1pAA6PersonCvp : $*Person // user: %7 把person全局变量给%3寄存器
%4 = metatype $@thick Person.Type // user: %6 %4 赋值为Person.Type 的元类型
// function_ref Person.__allocating_init()//执行init方法函数
%5 = function_ref @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person // user: %6
//%5相当于方法函数的指针地址
%6 = apply %5(%4) : $@convention(method) (@thick Person.Type) -> @owned Person // user: %7
// 通过申请 init方法中传入Person.Type的类型 生成实例变量 赋值给%6
store %6 to %3 : $*Person // id: %7
//store 存储,把我们实例的对象 我们之前定义的全局变量%3中 只能*Person
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
//$Int32 值相当于 0 ,类似我们oc中 main return 0
return %9 : $Int32 // id: %10
} // end sil function 'main'
复制代码
上面的%0,%1
等是虚拟的寄存器
。在SIL文件中,你会看到很多不懂的关键词,你可以查看GitHub上的官方文档查阅
- 脚本编译打开
我们也可以添加脚本运行打开
1.添加target
添加运行脚本
添加脚本,这里需要在当前main所在的文件夹
swiftc -emit-sil ${SRCROOT}/*项目文件夹*/main.swift | xcrun swift-demangle >
./main.sil && open main.sil
复制代码
报错的话,或者说无法打开,你可以先打开vscode
,设置main.sil
的打开方式
2 汇编分析流程
我们使用汇编进行分析
我们在Person类初始化前打断点,然后在__allocating_init()
处打断点
setp into 当前的方法 ,按住control 点击进入
进入后会调用init方法进行初始化
进入init
3.源码分析
接下来我们来看一下源码。源码可以去苹果官网下-swift源码下载地址。用 VSCode 打开下载好的 swift 源码,全局搜索 swift_allocObject
这个函数。
我们就像看下 _swift_allocObject_
的实现,主要传3个参数,根据分配的内存大小通过swift_slowAlloc
创建内存空间,之后把元数据关联内存空间,最后返回这个实例对象。
点击进入swift_slowAlloc
查看
对齐方式默认大小
上面我们最终生成一个heapObject的对象,类似我没OC中类继承与objc_class一样
- HeapObject
主要有2个初始化的方法,都包含2个参数分别时HeapMetadata
类型的metaData
和InlineRefCounts
类型的refcounts
HeapMetadata
查看TargetHeapMetadata
兼容了oc中的类,数据元为isa
;swift类的话默认MetaDataKind
类型的数据
查看#include "MetadataKind.def"
得到对应的值
name Value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF
复制代码
继续查看TargetHeapMetadata
继承的TargetMetadata
,在C++中结构体可以继承
查看TargetMetadata
里面有很多方法属性,我们关注下getTypeContextDescriptor
的方法
根据MetaDataKind
的类型获取不同的des
,当 kind
是一个 Class
的时候,会拿到一个名为 TargetClassMetadata
的指针,我们看看 TargetClassMetadata
的实现:
继承TargetAnyClassMetadata
,点击查看
如果是oc类,这个TargetAnyClassMetadata
这个结构和我们oc中类
的结构类似isa
,superClass
,cache
,bits(data)。
所以我们总结下swift的数据结构
struct Metadata {
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
复制代码
前面我们分析可知初始化生成一个heapObject
的对象,我们可以仿照这个结构体进行自定义
class Person{
var name = "fish"
var age = 3
}
struct HeapObject{
var metadata: UnsafeRawPointer
var refCounts: UInt32
}
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
let p = Person()
let objcRawPtr = Unmanaged.passUnretained(p as AnyObject).toOpaque()//获取实例对象的指针
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self
, capacity: 1)//把HeapObject转换我们自定义的MetaData结构
print(objcPtr.pointee);
复制代码
我们使用lldb验证下
打印下metadata
结构体内存说明类本质是一个结构体
4.总结
- swift中的类本质是
heapObject
类型的结构体包含metadata
和引用计数
,我么oc中的类本质是objc_class
类型的结构体。 - swift中实例对象存贮的是
metadata
和引用计数
,成员变量
。oc中实例变量存储的是isa
和成员变量
- swift中
metadata
中包含类的信息通过getTypeContextDescriptor
获取描述的类型把数据存在对应的结构体中。 - 大致流程