通过源码看 Swift 中的类、对象、属性
swift 类、对象、属性
Step 1 swift 源码编译
坑很多,编译时间长(顺利的话 3 小时以内,错一次重新等1小时,为了不影响工作建议专门准备一台电脑跑),编译后项目占20G 到 60G 不等,看你的编译方式,可以先按照我的步骤尝试,如果遇到问题可以参考我搜罗的各种博客包括官方指南,每个人遇到的问题都不一样,每个版本的坑也不同,非常奇特。
以 swift 5.7 release 为例
不是每个版本都能成功编译,也不代表别人能能编译的版本你就能编译,想快速成功尽量保证环境一致。 我的环境 swift-driver version: 1.62.15 Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51) Target: arm64-apple-macosx13.0
- 安装 cmake、 ninja、 sccache
brew install cmake ninja sccache
- 拉取 swift 仓库代码
新建一个仓库文件夹 “swift-build”
cd swift-build
git clone --branch swift-5.7-RELEASE git@github.com:apple/swift.git
- 拉取依赖 (大概用了一两个小时,用梯子应该更快)
首先切到 swift 文件夹下,官方提供的脚本、源码都在这里。
cd swift
utils/update-checkout --tag swift-5.7-RELEASE --clone
以上步骤建议终端内全程挂梯子
- 编译(1 到 2 小时)
utils/build-script --skip-build-benchmarks \
--skip-ios --skip-watchos --skip-tvos --swift-darwin-supported-archs "$(uname -m)" \
--sccache --release-debuginfo --swift-disable-dead-stripping
要理解参数含义,可使用命令查看,或者参考下方提供的博客
utils/build-script --help
- 打开工程
编译完成后项目文件夹下会生成一个 build 文件夹。
打开
swift-build/build/Xcode-RelWithDebInfoAssert+stdlib-DebugAssert/swift-macosx-arm64
下的
Swift.xcodeproj
- 新建target
自定义一个名称,例如 SwiftBuild。
添加 Target Dependencies
- 修改scheme
8.run
20-30分钟左右。
我遇到的问题
发现是目录文件下缺少这两个库,然后在
swift-build/build/Ninja-RelWithDebInfoAssert/swift-macosx-arm64/lib
下找到了这两个库,拷过去就可以了
如果遇到其他问题可参考下面的网站,遇到问题很正常,多查一下资料,检查环境问题,快则3小时搞定,慢的花上一周的也有。
参考网站
How to Set Up an Edit-Build-Test-Debug Loop (推荐)
编译 Swift 5.8 源码(推荐)
Step 2 对象的创建
写一个简单的类,通过断点方式查看对象的调用
step into
此处系统调用了 swift_allocObject 传入了三个参数,分别为
- metadata
- requiredSize
- requiredAlignmentMask
using HeapMetadata = TargetHeapMetadata<InProcess>;
后两者不难理解,分别是对象需要的空间大小(此处为 40 字节) 以及字节对齐掩码(此处为7 即 1000)
而前者 metadata 不难猜测就是对象实例的结构体,查看HeapMetadata,发现它是 TargetHeapMetadata
深入看看
所有堆分配类型的元数据的共同结构。可以通过加载任何堆对象的’isa’字段来检索其中之一的指针,无论它是由Swift还是Objective-C管理的。但是,当从Objective-C对象加载时,此元数据可能没有堆元数据头,并且可能不是对象动态类型的Swift类型元数据。
TargetHeapMetadata 只有两个初始化方法,继续回溯它的父类 TargetMetadata。
观察到 TargetMetadata 只有一个属性 Kind,通过 SetKind 方法可以了解到,它的类型是 MetadataKind,一个uint32_t,为了探究 Kind 的作用,搜索 MetadataKind,找到了一个关键函数
getTypeContextDescriptor()
这个函数根据 TargetMetadata 的 Kind 将自身分为了几种不同的类型,其中 Class 类型为 TargetClassMetadata
根据注释可以了解到 TargetClassMetadata 兼容 swift 与 OC 的类,在 swift 下 TargetClassMetadata 继承自 TargetAnyClassMetadata
至此我们找到了 Class 类型的真实结构
结合TargetAnyClassMetadata,概览一下TargetClassMetadata的成员
struct TargetClassMetadata {
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32 // swift特有的标志
var instanceAddressPoint: UInt32 // 实例独享内存首地址
var instanceSize: UInt32 // 实例对象内存大小
var instanceAlignmentMask: UInt16 // 实例对象内存对齐方式
var reserved: UInt16 // 运行时保留字段
var classSize: UInt32 // 类的内存大小
var classAddressPoint: UInt32 //类的内存首地址
var typeDescriptor: UnsafeMutablePointer<TargetClassDescriptor> // 类型描述器
var iVarDestroyer: UnsafeRawPointer // 实例销毁器
}
用与上面相同的方法将TargetClassDescriptor的成员梳理出来
// 类描述器
struct TargetClassDescriptor{
var flags: UInt32
var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
var name: TargetRelativeDirectPointer<CChar> // class/struct/enum 的名称
var accessFunctionPointer: TargetRelativeDirectPointer<UnsafeRawPointer>
var fieldDescriptor: TargetRelativeDirectPointer<FieldDescriptor> // 属性描述器
var superClassType: TargetRelativeDirectPointer<CChar> // 父类类型
var metadataNegativeSizeInWords: UInt32
var metadataPositiveSizeInWords: UInt32
var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32 // 每一个属性值距离当前实例对象地址的偏移量
//var Offset: UInt32
// var size: UInt32
// V-Table (methods)
func getFieldOffsets(_ metadata: UnsafeRawPointer) -> UnsafePointer<Int> {
return metadata.assumingMemoryBound(to: Int.self).advanced(by: numericCast(self.fieldOffsetVectorOffset))
}
// 泛型参数的偏移量
var genericArgumentOffset: Int { return 2 }
}
继续梳理出 TargetRelativeDirectPointer 与 FieldDescriptor 的结构
// 相对地址信息 - 存储的是偏移量
struct TargetRelativeDirectPointer<Pointee> {
var offset: Int32
mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee>{
let offset = self.offset
return withUnsafePointer(to: &self) { p in
/*
获得self,变为raw,然后+offset
- UnsafeRawPointer(p) 表示this
- advanced(by: numericCast(offset) 表示移动的步长,即offset
- assumingMemoryBound(to: T.self) 表示假定类型是T,即自己指定的类型
- UnsafeMutablePointer(mutating:) 表示返回的指针类型
*/
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
}
}
}
// 属性描述器
struct FieldDescriptor {
var mangledTypeName: TargetRelativeDirectPointer<CChar>
var superclass: TargetRelativeDirectPointer<CChar>
var kind: UInt16
var fieldRecordSize: UInt16
var numFields: UInt32 // 属性个数
var fields: FiledRecordBuffer<FieldRecord> // 属性列表
}
// 属性
struct FieldRecord {
var flags: Int32
var mangledTypeName: TargetRelativeDirectPointer<CChar> // 属性的类型
var fieldName: TargetRelativeDirectPointer<UInt8> // 属性的名称
}
struct FiledRecordBuffer<Element>{
var element: Element
mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
return withUnsafePointer(to: &self) {
let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
return start
}
return UnsafeBufferPointer(start: ptr, count: n)
}
}
mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
至此我们还原出了 TargetClassMetadata 的完整结构。
接下来将它们声明在常规工程中,我们就可以通过类型打印出它的属性名列表了
验证 TargetClassMetadata 的结构
class Person {
var name = "steve"
var age = 56
}
var pClazz = Person.self // 拿到类对象
// 拿到ClassMetadata指针
let classMetadata_ptr = unsafeBitCast(pClazz as Any.Type, to: UnsafeMutablePointer<TargetClassMetadata>.self)
// 属性个数
let numFileds = classMetadata_ptr.pointee.typeDescriptor.pointee.numFields
// 拿到首个属性偏移地址信息
let offsets = classMetadata_ptr.pointee.typeDescriptor.pointee.getFieldOffsets(UnsafeRawPointer(classMetadata_ptr).assumingMemoryBound(to: Int.self))
for i in 0..<numFileds {
// 获取属性名
let fieldName = classMetadata_ptr.pointee.typeDescriptor.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.fieldName.getmeasureRelativeOffset()
print("fieldName:\(String(cString: fieldName))")
// 混写过的类型名称
let mangledTypeName = classMetadata_ptr.pointee.typeDescriptor.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.mangledTypeName.getmeasureRelativeOffset()
print("mangledTypeName:\(String(cString: mangledTypeName))")
print("==================")
}
如何获取属性的类型信息呢?
来到 MetadataLookup.cpp
这个类里提供了一系列 metadata 属性类型的反射方法,其中就包括接下来要用到的
使用@_silgen_name(“swift_getTypeByMangledNameInContext”) 函数映射
@_silgen_name("swift_getTypeByMangledNameInContext")
public func _getTypeByMangledNameInContext(
_ name: UnsafePointer<CChar>,
_ nameLength: Int,
context: UnsafeRawPointer?,
genericArguments: UnsafeRawPointer?
) -> Any.Type?
即可通过这个方法获取属性的类型
// 泛型参数
let genericVector = UnsafeRawPointer(classMetadata_ptr).advanced(by: classMetadata_ptr.pointee.typeDescriptor.pointee.genericArgumentOffset * MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: Any.Type.self)
// 获取属性类型:
let fieldType = _getTypeByMangledNameInContext(mangledTypeName,
256,
context: UnsafeRawPointer(classMetadata_ptr.pointee.typeDescriptor),
genericArguments: genericVector)
print(fieldType as Any)
函数表
来到 GenMeta.cpp
发现 VTable 是拼接在 Metadata 之后。
Swift 属性
- 存储属性
- 计算属性
- 延迟属性
- 类属性
class Programer {
var name: String = "steve"
private var _weight = 0
var weight: Int {
get{return 50}
set{_weight = newValue}
}
lazy var age = 56
static var height = 180
}
通过 SIL 转译后变为
class Programer {
@_hasStorage @_hasInitialValue var name: String { get set }
@_hasStorage @_hasInitialValue private var _weight: Int { get set }
var weight: Int { get set }
lazy var age: Int { get set }
@_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
@_hasStorage @_hasInitialValue static var height: Int { get set }
@objc deinit
init()
}
- @_hasStorage 代表属性实际存储了值
- @_hasInitialValue 代表属性有初始值
- lazy 类型变为了一个 optional 的变量
继续阅读下面的 SIL 代码
// variable initialization expression of Programer.name
sil hidden [transparent] @$s14Properties_SIL9ProgramerC4nameSSvpfi : $@convention(thin) () -> @owned String {
bb0:
%0 = string_literal utf8 "steve" // user: %5
%1 = integer_literal $Builtin.Word, 5 // user: %5
%2 = integer_literal $Builtin.Int1, -1 // user: %5
%3 = metatype $@thin String.Type // user: %5
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %5
%5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
return %5 : $String // id: %6
} // end sil function '$s14Properties_SIL9ProgramerC4nameSSvpfi'
此处为 name 的初始化过程,最终返回的%5寄存器内的值来自对%4的调用,通过注释了解到,它实际是调用了String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)方法,
三个参数分别为
- string 的字面量值
- utf8CodeUnitCount 字符串长度
- isASCII 是否为 ASCII 编码
实际调用的时候还传递了一个 String 元类的参数,即 %3 寄存器存放的值。
继续观察 name 的 getter 与 setter 方法
// Programer.name.getter
sil hidden [transparent] @$s14Properties_SIL9ProgramerC4nameSSvg : $@convention(method) (@guaranteed Programer) -> @owned String {
// %0 "self" // users: %2, %1
bb0(%0 : $Programer):
debug_value %0 : $Programer, let, name "self", argno 1, implicit // id: %1
%2 = ref_element_addr %0 : $Programer, #Programer.name // user: %3
%3 = begin_access [read] [dynamic] %2 : $*String // users: %4, %6
%4 = load %3 : $*String // users: %7, %5
retain_value %4 : $String // id: %5
end_access %3 : $*String // id: %6
return %4 : $String // id: %7
} // end sil function '$s14Properties_SIL9ProgramerC4nameSSvg'
// Programer.name.setter
sil hidden [transparent] @$s14Properties_SIL9ProgramerC4nameSSvs : $@convention(method) (@owned String, @guaranteed Programer) -> () {
// %0 "value" // users: %11, %8, %4, %2
// %1 "self" // users: %5, %3
bb0(%0 : $String, %1 : $Programer):
debug_value %0 : $String, let, name "value", argno 1, implicit // id: %2
debug_value %1 : $Programer, let, name "self", argno 2, implicit // id: %3
retain_value %0 : $String // id: %4
%5 = ref_element_addr %1 : $Programer, #Programer.name // user: %6
%6 = begin_access [modify] [dynamic] %5 : $*String // users: %8, %7, %10
%7 = load %6 : $*String // user: %9
store %0 to %6 : $*String // id: %8
release_value %7 : $String // id: %9
end_access %6 : $*String // id: %10
release_value %0 : $String // id: %11
%12 = tuple () // user: %13
return %12 : $() // id: %13
} // end sil function '$s14Properties_SIL9ProgramerC4nameSSvs'
通过 SIL 不难理解他们的流程,
- debug_value 代表将寄存器的值与后面定义的变量绑定
- ref_element_addr 代表获取元素地址
- begin_access/end_access 代表开始/结束访问
- load 将值从内存中放入寄存器
- store 将寄存器的值放入内存
注意setter方法最后返回了一个空元组,以下是 chatgpt 的解释,谨慎参考
SIL(Swift Intermediate Language)是Swift语言的一种中间表示形式,用于编译器优化和代码生成。在SIL中,所有函数都是以标准化的形式表示的,包括getter和setter方法。
在Swift语言中,setter方法通常用于修改对象的属性。在SIL中,setter方法的返回类型是一个元组类型,即空元组() -> ()。这是因为setter方法的主要目的是修改对应属性的值,并不需要返回任何值。
此外,返回一个空元组也有助于编译器进行优化,减少不必要的内存分配和拷贝操作。
再来看 lazy var 的流程
// Programer.age.getter
sil hidden [lazy_getter] [noinline] @$s14Properties_SIL9ProgramerC3ageSivg : $@convention(method) (@guaranteed Programer) -> Int {
// %0 "self" // users: %14, %2, %1
bb0(%0 : $Programer):
debug_value %0 : $Programer, let, name "self", argno 1, implicit // id: %1
%2 = ref_element_addr %0 : $Programer, #Programer.$__lazy_storage_$_age // user: %3
%3 = begin_access [read] [dynamic] %2 : $*Optional<Int> // users: %4, %5
%4 = load %3 : $*Optional<Int> // user: %6
end_access %3 : $*Optional<Int> // id: %5
switch_enum %4 : $Optional<Int>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6
// %7 // users: %9, %8
bb1(%7 : $Int): // Preds: bb0
debug_value %7 : $Int, let, name "tmp1", implicit // id: %8
br bb3(%7 : $Int) // id: %9
bb2: // Preds: bb0
%10 = integer_literal $Builtin.Int64, 56 // user: %11
%11 = struct $Int (%10 : $Builtin.Int64) // users: %18, %13, %12
debug_value %11 : $Int, let, name "tmp2", implicit // id: %12
%13 = enum $Optional<Int>, #Optional.some!enumelt, %11 : $Int // user: %16
%14 = ref_element_addr %0 : $Programer, #Programer.$__lazy_storage_$_age // user: %15
%15 = begin_access [modify] [dynamic] %14 : $*Optional<Int> // users: %16, %17
store %13 to %15 : $*Optional<Int> // id: %16
end_access %15 : $*Optional<Int> // id: %17
br bb3(%11 : $Int) // id: %18
// %19 // user: %20
bb3(%19 : $Int): // Preds: bb2 bb1
return %19 : $Int // id: %20
} // end sil function '$s14Properties_SIL9ProgramerC3ageSivg'
它的特殊之处在于取值时对寄存器内的值有一个判断,如果不为空则直接返回值,如果为空则进行初始化流程,注意此处的流程没有线程安全的保证,意味着lazy var的初始化不是线程安全的。
最后看 static 属性
// one-time initialization function for height
sil private [global_init_once_fn] @$s14Properties_SIL9ProgramerC6height_WZ : $@convention(c) () -> () {
bb0:
alloc_global @$s14Properties_SIL9ProgramerC6heightSivpZ // id: %0
%1 = global_addr @$s14Properties_SIL9ProgramerC6heightSivpZ : $*Int // user: %4
%2 = integer_literal $Builtin.Int64, 180 // user: %3
%3 = struct $Int (%2 : $Builtin.Int64) // user: %4
store %3 to %1 : $*Int // id: %4
%5 = tuple () // user: %6
return %5 : $() // id: %6
} // end sil function '$s14Properties_SIL9ProgramerC6height_WZ'
staic 属性使用了alloc_global,意味着它是在全局数据区进行初始化的,此外它自动生成并插入了一个函数 global_init_once_fn ,它能确保全局变量只被初始化一次,因此 static 是线程安全的。