swift进阶十:Mirror源码解析
2020-12-20 本文已影响0人
markhetao
上一节,我们简单体验了Mirror反射机制。
本节,通过源码,深入探索Mirror反射 💪
- 初始化
- 属性类型
- 属性个数
- 属性值
-
Mirror(反射):
可以动态获取类型、成员变量,在运行时可以调用方法、属性等行为的特性。
回顾
Mirror的简单使用:class HTPerson { var name = "ht" var age = 18 } let p = HTPerson() let mirror = Mirror(reflecting: p.self) for pro in mirror.children { print("\(pro.label ?? ""):\(pro.value)") }
- 打印结果:
image.png
1. 初始化
我们知道,OC中通过Runtime能轻松获取属性、方法、协议等,但swift的Mirror是如何获取到属性类型、个数、值的呢?
-
带着这些疑问,我们在
swift源码中搜索Mirror.swift:
image.png
-
我们
搜索并找到初始化方法public init(reflecting subject: Any),从这里开始我们的探索:
image.png
-
首先,我们分析一下如何通过
subject来获取类型。
2. 类型获取
- 搜索
_getNormalizedType:
image.png
注意:
源码分析时,首先分析结构,结构是由继承链上的所有属性共同决定的。而
函数只是为所有属性服务的。所以我们的重心,就是先找到结构,尝试重写结构,能使用自己的结构完整读取到相应的内容,证明源码分析的结果是正确的。在这个过程,
顺道看一眼函数,在属性操作时,回头分析哪些函数操作了它,是如何实现的。
这样才能完整的梳理清楚。(当然,得花大量时间反复研究才行)
- 根据
源码探索,以struct类型为例,可知structMetaData结构为:
image.png
- 按照
structMetadata格式,尝试读取struct的类型:
(get:仿照源码中RelativeDirectPointer (相对位置指针)进行偏移,获取真实内存值)
struct StructMetadata {
var kind: Int
var description: UnsafeMutablePointer<StructMetadataDesc>
}
struct StructMetadataDesc {
var flags: UInt32
var parent: UInt32 // 展示用Uint32代替,实际是相同大小的结构体,
var name: RelativeDirectPointer<CChar>
// . . . (当前研究获取属性类型,后面的属性先不管)
}
struct RelativeDirectPointer<T>{
var offset: Int32
// 偏移offset位置,获取内容指针
mutating func get() -> UnsafeMutablePointer<T> {
let offset = self.offset
// withUnsafePointer获取指针
return withUnsafePointer(to: &self) { p in
// UnsafeMutablePointer 返回T类型对象的指针
// UnsafeRawPointer将p指针转换为未知类型
// numericCast将offset转换为偏移单位数
// advanced进行内存偏移
// assumingMemoryBound绑定指针为T类型
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}
struct HTStruct {
var age = 18
}
// 读取将HTStuct指针内容,赋值给StructMetadata (unsafeBitCast: 通过字节读取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
// 读取当前name的内容指针
let namePtr = p.pointee.description.pointee.name.get()
// name是CChar类型,转为字符串输出
print(String(cString: namePtr))
- 成功获取
HTStruct类型:
image.png
3. 属性个数
-
回到
初始化方法,可以看到是通过_getChildCount获取属性个数:
image.png
-
仿写代码,
struct StructMetadata {
var kind: Int
var description: UnsafeMutablePointer<StructMetadataDesc>
}
struct StructMetadataDesc {
var flags: UInt32
var parent: UInt32 // 展示用Uint32代替,实际是相同大小的结构体,
var name: RelativeDirectPointer<CChar> // 不在乎具体类型,就先用UnsafeRawPointer
var accessFunctionPtr: RelativeDirectPointer<UnsafeRawPointer> // 不在乎具体类型,就先用UnsafeRawPointer
var fields: RelativeDirectPointer<UnsafeRawPointer> // 不在乎具体类型,就先用UnsafeRawPointer
var numFields: UInt32 // 属性个数
var fieldOffsetVectorOffset: UInt32
}
struct RelativeDirectPointer<T>{
var offset: Int32
// 偏移offset位置,获取内容指针
mutating func get() -> UnsafeMutablePointer<T> {
let offset = self.offset
// withUnsafePointer获取指针
return withUnsafePointer(to: &self) { p in
// UnsafeMutablePointer 返回T类型对象的指针
// UnsafeRawPointer将p指针转换为未知类型
// numericCast将offset转换为偏移单位数
// advanced进行内存偏移
// assumingMemoryBound绑定指针为T类型
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}
struct HTStruct {
var age = 18
}
// 读取将HTStuct指针内容,赋值给StructMetadata (unsafeBitCast: 通过字节读取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
// 读取当前name的内容指针
let namePtr = p.pointee.description.pointee.name.get()
let count = p.pointee.description.pointee.numFields
// name是CChar类型,转为字符串输出
print(String(cString: namePtr))
print("HTStruct属性个数:\(count)")
-
打印结果:
image.png
-
修改结构体属性个数后:
image.png
成功的重写结构,拿到属性个数,下面,我们来分析属性值的获取
4. 属性值
- 属性值的获取,也是在
children获取时得到。我们循着路径,找到对应类型的impl,读取description里面的fields,分析fields内部结构,发现每个属性是以FieldRecord格式进行存储。 存放的位置在fields的最后。有几个属性就会新增几条FieldRecord记录,可通过内存地址偏移获取所有属性名。
image.png
// 结构体类型
struct StructMetadata {
var kind: Int
var description: UnsafeMutablePointer<StructMetadataDesc>
}
// 结构体类型的描述
struct StructMetadataDesc {
var flags: UInt32
var parent: UInt32 // 展示用Uint32代替,实际是相同大小的结构体,
var name: RelativeDirectPointer<CChar> // 不在乎具体类型,就先用UnsafeRawPointer
var accessFunctionPtr: RelativeDirectPointer<UnsafeRawPointer> // 不在乎具体类型,就先用UnsafeRawPointer
var fields: RelativeDirectPointer<FieldDescription> // 记录所有属性内容
var numFields: UInt32 // 属性个数
var fieldOffsetVectorOffset: UInt32
}
// 记录结构体内所有属性的结构
struct FieldDescription {
var MangledTypeName: RelativeDirectPointer<CChar>
var Superclass: RelativeDirectPointer<CChar>
var Kind: UInt16
var FieldRecordSize: UInt16
var NumFields: UInt32
var fields: FieldRecord // 连续存储空间 (有几个数据,就会在后面添加几个记录,通过内存平移读取)
}
// 每个属性的内容
struct FieldRecord {
var flag: Int32
var mangledTypeName: RelativeDirectPointer<CChar>
var fieldName: RelativeDirectPointer<CChar> // 属性名称
}
// 相对位移指针
struct RelativeDirectPointer<T>{
var offset: Int32
// 偏移offset位置,获取内容指针
mutating func get() -> UnsafeMutablePointer<T> {
let offset = self.offset
// withUnsafePointer获取指针
return withUnsafePointer(to: &self) { p in
// UnsafeMutablePointer 返回T类型对象的指针
// UnsafeRawPointer将p指针转换为未知类型
// numericCast将offset转换为偏移单位数
// advanced进行内存偏移
// assumingMemoryBound绑定指针为T类型
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}
struct HTStruct {
var age = 18
var name = "ht"
}
// 读取将HTStuct指针内容,赋值给StructMetadata (unsafeBitCast: 通过字节读取)
let p = unsafeBitCast(HTStruct.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
// 读取当前name的内容指针
let namePtr = p.pointee.description.pointee.name.get()
let count = p.pointee.description.pointee.numFields
print("类型:\(String(cString: namePtr))") // name是CChar类型,转为字符串输出
print("属性个数:\(count)")
// 单独读取第一个属性名
var fields = p.pointee.description.pointee.fields.get()
let fieldRecord1Name = fields.pointee.fields.fieldName.get()
print(String(cString: fieldRecord1Name))
// 读取所有记录
print("----读取所有属性名----")
(0..<count).forEach { index in
let recordPtr = withUnsafePointer(to: &fields.pointee.fields) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: Int(index)))
}
print(String(cString: recordPtr.pointee.fieldName.get()))
}
print("----dump----")
dump(HTStruct()) // 相似的实现方式
-
本文从
struct结构开始入手分析,通过源码。从初始化方法开始,找到属性类型、属性个数、属性值。成功还原相应数据格式。初步窥探Mirror的内部实现。 -
源码的世界,都是大师级制作。每次拜读和分析,都能学到一些思维方式。本文仅仅作为一个引路篇,更多有意思的东西,还需要我们自己慢慢探索。
image.png