上一节,我们简单体验
了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)") }
- 打印结果:
1. 初始化
我们知道,OC
中通过Runtime
能轻松获取属性
、方法
、协议
等,但swift
的Mirror
是如何获取到属性类型
、个数
、值
的呢?
-
带着这些疑问,我们在
swift源码
中搜索Mirror.swift
:
-
我们
搜索
并找到
初始化方法public init(reflecting subject: Any)
,从这里开始我们的探索:
首先,我们分析一下如何通过
subject
来获取类型
。
2. 类型获取
- 搜索
_getNormalizedType
:
注意:
源码分析
时,首先
分析结构
,结构是由继承链
上的所有属性
共同决定的。而
函数
只是为
所有属性服务
的。所以我们的重心
,就是先找到结构
,尝试重写结构
,能使用自己的结构完整读取
到相应的内容
,证明
源码分析
的结果是正确
的。在这个过程,
顺道
看一眼函数
,在属性操作
时,回头分析
哪些函数
操作了它,是如何实现
的。
这样才能完整
的梳理清楚
。(当然,得花大量时间反复研究才行)
- 根据
源码探索
,以struct类型
为例,可知structMetaData
结构为:
- 按照
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
类型:
3. 属性个数
-
回到
初始化方法
,可以看到是通过_getChildCount
获取属性个数:
仿写代码,
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)")
-
打印结果:
-
修改
结构体属性个数
后:
成功的重写结构
,拿到属性个数
,下面,我们来分析属性值
的获取
4. 属性值
- 属性值的获取,也是在
children
获取时得到。我们循着路径,找到对应类型的impl
,读取description
里面的fields
,分析fields
内部结构,发现每个属性
是以FieldRecord
格式进行存储。 存放的位置在fields
的最后。有几个属性
就会新增几条FieldRecord
记录,可通过内存地址偏移
获取所有属性名
。
// 结构体类型
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
的内部实现。源码的世界
,都是大师级制作
。每次拜读和分析,都能学
到一些思维方式
。本文仅仅作为一个引路篇
,更多有意思的东西,还需要我们自己慢慢探索。