「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战。」
本篇文章来分析Swift 类底层的源码结构
Swift代码编译流程:
iOS 开发的语言不管是 OC 还是 Swift 后端都是通过LLVM
进行编译的,如下图所示:
- OC 通过 clang 编译器编译成 IR, 然后再生成可执行文件.o
- Swift 则是通过 Swift 编译器编译成 IR,然后再生成可执行文件 .o
- 先是 Swift Code 通过
-dump-parse
进行语义分析解析成Parse
(抽象语法树) - Parse 经过
-dump-ast
进行语义分析语法是否正确 - Sema 之后会把
Swift Code
降级变成SILGen
(Swift 中间代码),对于 SILGen 又分为未优化(-emit-silgen)和经过优化(-emit-sil) - 优化完的 SILGen 会有 LLVM 降级 成为
IR
,然后 IR 再由后端代码编译成机器码
编译流程的命令:
// 分析输出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
复制代码
查看编译称SIL后的代码
class Person {
var age = 20
var name = "Candy"
}
let person = Person()
复制代码
进入到当前代码所在的位置,输入 swiftc main.swift -emit-sil
然后回车
关于SIL的语法说明,可以参考官方文档:github.com/apple/swift…
查看汇编代码
通过下面这样,打上代码断点后可以查看汇编代码
接下来打开汇编调试
通过汇编查看,Person 在进行初始化的时候,在底层会调用 __allocating_init
的函数,那么 __allocating_init 做了什么事情呢,跟进去看一下。在圈住的地方打一个断点,让断点走到 __allocating_init 这一行代码,按住 control 键,点击这个向下的按钮。
其实进入这个页面也看不出来什么
这个时候我们来看一下源码
。源码可以去苹果官网下-swift源码下载地址。用 VSCode 打开下载好的 swift 源码,全局搜索 swift_allocObject
这个函数。
在 VSCode 打开 Swift 源码,如果想像 Xcode 一样,点击进行实现跳转,需要安装插件 C/C++ Extension Pack
。
在 HeapObject.cpp
文件中找到 swift_allocObject
函数的实现,并且在 swift_allocObject 函数的实现上方,有一个 _swift_allocObject_
函数的实现。
在函数的内部会调用一个 swift_slowAlloc
函数,我们来看下 swift_slowAlloc
函数的内部实现:
swift_slowAlloc
函数的内部是去进行一些分配内存的操作,比如 malloc
。所以就印证了引用类型->对象申请堆空间
的过程。
OC 与 Swift 的区分调用
在调用 _swift_allocObject_
函数的时候有一个参数,名为 metadata
的 HeapMetadata
。以下是 HeapMetadata
跟进的代码过程:
在这里有对 OC 和 Swift 做兼容。调用的 TargetHeapMetadata 函数的时候,如果是 Swift 类,那么就是MetadataKind 类型参数, 如果是 OC 的类,那么参数为 isa 指针。MetadataKind 是一个 uint32_t 的类型。
那么 MetadataKind
的种类如下:
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
复制代码
Swift 类底层的源码结构
接下来我们找到 TargetHeapMetadata
的继承 TargetMetadata
(在 C++ 中结构体是允许继承的)。在 TargetMetadata
结构体中找到了 getTypeContextDescriptor
函数,代码如下:
可以看到,当 Metadatakind
是一个 Class 的时候,会拿到一个名为 TargetClassMetadata
的指针,来看看 TargetClassMetadata
的实现:
我们看到 TargetClassMetadata 继承于 TargetAnyClassMetadata,在这里看到了superclass
、isa
。
通过以上的分析,我们可以得出,Swift 类中的 metadata 数据结构大致如下:
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
}
复制代码
接下来我们做一个测试,通过 lldb 查看 Swift 类的内存结构,那么既然在 Swift 的底层,_swift_allocObject_
函数返回的是 HeapObject
的指针类型,我们来看一下 HeapObject
的结构:
知道了 HeapObject
的源码结构之后,我们也假里假气的模仿源码,自己定义一个 HeapObject
,refCounts
可以先忽略,不管,主要看 metadata
,通过上面看到的 Metadata 也还原出对应的源码。 我们将 Person 类转成 HeapObject 结构体,来打印HeapObject和Metadata
的内存结构。
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
}
class Person {
var age = 20
var name = "Candy"
}
let person = Person()
// 将 person 转成 HeapObject 指针
let p_raw_ptr = Unmanaged.passRetained(person as AnyObject).toOpaque()
let p_ptr = p_raw_ptr.bindMemory(to: HeapObject.self, capacity: 1)
// 将 p_ptr 指针转成 HeapObject 的指针类型并打印出 HeapObject 的内存结构
print(p_ptr.pointee)
//将HeapObject中的metadata绑定成Metadata类型,并转成Metadata指针类型,数据大小可以使用MemoryLayout进行测量
let matadata = p_ptr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(matadata)
复制代码
通过打印验证了我们还原出来的底层结构是没有问题的。
这里补一个小知识点:
可以通过以下方法获取对象的指针
withUnsafeBytes(of: &person) { (ptr) -> Void in
}
withUnsafePointer(to: &person) { (ptr) -> Void in
}
复制代码
假如通过这种方法传递对象的指针,就会在将指针转换成class对象的时候遇到问题。所以,这里需要用到Unmanaged。
Unmanaged
是一个结构体,可以用来获取一个对象的指针,也可以将一个指针转换成一个对象:
// 获取指针
let ptr = UnsafeMutableRawPointer(Unmanaged<A>.passUnretained(person).toOpaque())
//获取对象
let obj = Unmanaged<A>.fromOpaque(ptr).takeUnretainedValue()
复制代码
总结
所以就可以得到下面的这些结论
Swift 对象内存分配的流程:
__allocating_init -> swift_allocObject -> _swift_allocObject_ ->
swift_slowAlloc -> Malloc
复制代码
Swift 对象的内存结构 HeapObject
(OC objc_object) ,有两个属性: 一个是 Metadata
,一个是 RefCount
,默认占用 16 字节大小。 而对于 OC 对象,只存在一个 isa
,默认占有8字节的大小。