1、元类型
1.1、AnyObject
- 任意类的 instance、类的类型、仅类遵守的协议(结构体不能遵守)
class LZPerson: Run { var age: Int = 20 var weight: Double = 181.5 func running() -> Self { print("person is running") return self } } protocol Run: AnyObject { func running() -> Self } let p = LZPerson() let p1 : AnyObject = p let p2 : AnyObject = LZPerson.self let age: AnyObject = (p as! LZPerson).age as NSNumber-
在和 OC 交互的过程中,也经常通过 AnyObject 来表示某种类型的 instance
-
在代码编写过程中不知道具体类型的时候用 AnyObject 表示,如果确定类型之后可以通过 as,as?(不确定类型时尝试转换,如果转换失败则返回nil),as! 三种关键字进行类型转换。
-
不能用 Int 等Swift底层是结构体的类型进行赋值
-
1.2、AnyClass
- Xcode代码中可以看到其实就是 AnyObject.Type 的类型别名
1.3、Any
- 代表任意类型,包括 funcation 类型或者 Optional 类型,对应的是
OC的id类型
1.4、type(of:T)
-
与 Self 基本可以理解为同一个东西,⽤来获取⼀个值的动态类型;需要理解下动静态类型,静态类型指的是在编译时期确定了的类型,而动态类型则是在运行时期才确定的类型
-
当协议P作为字符串的静态类型,传入一个泛型参数,并且未使用 As Any 转换时,会返回
P.self而不是String.self(系统解释的)- stringAsP字符串遵守P协议,被传入形参value,因为是 Any 类型所以编译期间编译器并不能知道这个形参的类型,在泛型函数的主体中,类型参数不是静态已知的协议类型。 因此,
type(of:)只能产生具体的元类型P.Protocol,要获得准确的动态类型,需要转换为 Any 类型
- stringAsP字符串遵守P协议,被传入形参value,因为是 Any 类型所以编译期间编译器并不能知道这个形参的类型,在泛型函数的主体中,类型参数不是静态已知的协议类型。 因此,
1.5、self
T.self:- T是实例对象,当前 T.self返回的就是他本身
- T是类,当前 T.self 返回的就是元类型
- T.self 属于 T.type
1.6、Self
-
用于直接获取当前类的元类型,然后可以用它直接访问类的类属性与类方法
-
在协议声明或协议成员声明中,Self 类型是指最终符合协议的类型
-
多用在 方法的返回值 和 协议 中
func running() -> Self { print("person is running") return self }
2、Swift中的Runtime
-
对于纯 Swift 类来说,方法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的Runtime 特性了。
-
对于纯 Swift 类,方法和属性添加
@objc标识的情况下,当前我们可以通过 Runtime API 拿到,但是在我们的 OC 中是没法进行调度的 -
对于继承自 NSObject 类来说,如果我们想要动态的获取当前的属性和方法,必须在其声明前添加@objc 关键字,否则也是没有办法通过Runtime APl 进行获取。
-
纯Swift类没有动态性,但在方法、属性前添加
dynamic修饰,可获得动态性。 -
继承自NSObject的Swift类,其继承自父类的方法具有动态性,其它自定义方法、属性想要获得动态性,需要添加dynamic修饰。
-
若方法的参数、属性类型为Swift特有、无法映射到Objective一C的类型(如Character、Tuple),则此方法、属性无法添加 dynamic 修饰(编译器报错)
3、Mirror
-
所谓反射就是可以
动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。在使⽤OC开发时很少强调其反射概念,因为OC的Runtime要⽐其他语⾔中的反射强⼤的多。但是 Swift 是⼀⻔类型安全的语⾔,不支持我们像OC那样直接操作,它的标准库仍然提供了反射机制来让我们访问成员信息,函数是不能直接访问到的,除非定义成属性信息 -
Swift 的反射机制是基于⼀个叫 Mirror 的 结构体 来实现的
Mirror(reflecting: Any).children:获取反射出的属性集合,.label属性名、.value属性值
3.1、案例应用
-
因为类中属性 也可能为包含其他属性的类或结构体等 ,所以对 child.value 值再调用 mirrorTest 方法进行递归操作,直到操作的属性 不再含有子属性,通过图中 guard 判断返回这个属性自身,供上一层的 result[key] 存为字典
-
gurad关键字:- 条件检测:⽐如我们在上⾯中的代码如果当前⼦属性为空,那么就直接返回⾃身,避免了 if
3.2、改进案例
- 为了能将转字典的方法抽取,提供给更多的对象使用,我们把具体方法放到 协议 里,并增加 抛出异常 的功能
- 如果我们想要展示更多的错误信息,系统给我们提供了
LocalizedError协议
- 如果我们想要展示更多的错误信息,系统给我们提供了
3.3、Mirror源码
说明CustomReflectable
- 首先找到初始化方法,看到一个
CustomReflectable协议,如果遵守则走协议流程,那它有什么作用呢?-
这⾥接受⼀个 Any 类型的参数,同样的这⾥有⼀个
if case的写法来判断当前的 subject 是否遵循了 customReflectable 协议,如果是我们就直接调⽤customMirror, 否则就进⾏下级函数的调⽤;遵循协议并实现后,po出来的会有具体值,而不仅是地址 -
if case : 其实是枚举 Case 的模式匹配,和我们的 Switch ⼀样,这⾥是只有⼀个 case 的 switch 语句
-
3.3.1、C++方法映射Swift方法
- Mirror 底层通过使用编译器隐藏符号
@_silgen_name,将C++方法映射到Swift方法 的方式,大量使用了C++方法,以下为部分映射关系,具体可到源码查询,命名方式都 以下划线"_"开始- 正常来讲调用一个C函数,我们需要在 .h中声明方法、.c中实现、桥接文件中导入.h文件
- 但使用 @_silgen_name后,桥接文件中不需导入.h, .h中也无需声明方法,
直接在.c中找实现方法
- 正常来讲调用一个C函数,我们需要在 .h中声明方法、.c中实现、桥接文件中导入.h文件
3.3.2、流程分析
- 根据上边init方法知道,不走 CustomReflectable 协议方法的话会进入
Mirror(internalReflecting:subject,subjectType,customAncestor)方法-
获取属性: 看到类型 subjectType 是由_getNormalizedType()方法得到,其中参数需要 subject及其真实类型,因为subject类型是 Any,所以需要使用type(of:)方法;对应的C++方法为:(传进来的subject及其类型在映射方法中变成了OpaqueValue与Metadata类型)- 再继续找到 call 函数的实现,这⾥其实是⼀个回调函数,当前回调的具体数据都是由
ReflectionMirrorImpl(抽象类)结构体实现,传递过来的 subject及其type存到impl中- 根据抽象类的具体类不同(
subject的真实类型调用getKind;如 StructImpl、EnumImpl 等),switch 进class, struct, enum, Tuple 等的具体实现
- 根据抽象类的具体类不同(
- ReflectionMirrorImpl 抽象类结构体 : 包含 属性数量、偏移量 等信息(具体实现还要看具体的Impl类型,这里是提供笼统的内容)
childMetadata与subscript方法也看一下:- getFieldAt():
- getFieldAt():
- 以
EnumImpl为例,看看除了基结构体的属性外还有哪些其他的:-
先看到的是一个
isReflectable的方法,这个方法表示 是否可以被反射,通过强转拿到EnumMetadata,再找到 Enum 中存储的Description,通过它里面存储的isReflectable来确定 -
数量由对抽象类 ReflectionMirrorImpl 的方法
intptr_t count()重写得到 -
再找到获取信息的
getInfo的方法 -
getFieldAt:获取Descriptor可以看到上一层 getFieldAt(type, tag) 传进来的 type 在这层中是
Metadata类型的,叫做 base,这下我们就知道了Mirror辗转一圈最后归结于获取 Metadata 数据结构;因为流程比较繁琐,下面拿到第4大点进行单独分析
-
- 再继续找到 call 函数的实现,这⾥其实是⼀个回调函数,当前回调的具体数据都是由
-
获取属性数量: 看到属性数量 childCount 由方法_getChildCount()得到;对应的C++方法为:- 可以与上边获取 type 的方法进行对比,发现差别只是 ReflectionMirrorImpl 的 return 中 impl 从 ->type 变成了 ->count()
-
获取存放属性信息的数组:
-
- 总结一下,流程分析其实就是分析怎么通过 ReflectionMirrorImpl 把属性的相关内容存到找到的,而获取信息的方法是通过访问 Metadata 实现
4、Metadata源码结构分析
4.1、StructMetadata
-
我们以 StructImpl 为例进行类似 EnumImpl 的分析,可以知道最终传到 getFieldAt() 方法中的 Metadata 是 StructMetadata,所以我们在源码中首先对其进行搜索(并逐步找寻父类),找到了这么一句:
原来底层将 StructMetadata 起了个别名叫
TargetStructMetadata,而且还给好多方法起了别名,随便看看就行,图中也仅是一小部分 -
TargetStructMetadata 结构体内容就在取别名的方法上边
- 这里
Description需要关注,涉及类型TargetStructDescriptor
- 这里
-
TargetValueMetadata
- 这里也有 Description 参数,涉及类型
TargetValueTypeDescriptor正是上边 TargetStructDescriptor的父类- 这里有两个重要属性:
NumFields与FieldOffsetVectorOffset- NumFields : 主要表示结构体中属性的个数,如果只有一个字段偏移量则表示偏移量的长度
- FieldOffsetVectorOffset : 表示这个结构体元数据中存储的属性的字段偏移向量的偏移量,如果是
0则表示没有
- 这里有两个重要属性:
- 这里也有 Description 参数,涉及类型
-
TargetMetadata:
Kind属性是一个UInt64(64位机器,32位机器则为UInt32) 指针
-
大概可以归纳 TagerStructMetadata 结构为
struct TagerStructMetadata { var kind:UnsafePointer<UInt64> var Description: UnsafeMutablePointer }
4.2、TargetValueTypeDescriptor
-
继续寻找父类内容
getKind:获取 Metadata 类型
-
TargetTypeContextDescriptorName: 就是类型的名称AccessFunctionPtr: 是该类型元数据访问函数的指针Fields: 是一个指向该类型的字段描述符的指针- 研究这个就需要跳到
FieldDescriptor中看一下,又能看到5个属性
- 研究这个就需要跳到
-
TargetContextDescriptor: descriptors 的最终基类Flags: 是描述上下文的标志,包括它的种类和格式版本Parent: 是记录父类上下文的,如果是顶级则为null,查看其类型TargetRelativeContextPointer<Runtime>发现是RelativeIndirectablePointer的别名RelativeIndirectablePointerRelativeOffsetPlusIndirect: 存储在内存中的对象属性存储相对的地址偏移量- 直接地址就是地址直接存0x1004,但偏移地址只需要存更少的内容只有一个4,再通过一个 基地址 ,就能访问到数据,具体可以在一个
applyRelativeOffset中查到
- 直接地址就是地址直接存0x1004,但偏移地址只需要存更少的内容只有一个4,再通过一个 基地址 ,就能访问到数据,具体可以在一个
4.3、属性名和属性值
- 3.3.2一开始的图中能看到获取属性的方法为
getChild,中间过程省略一下,与上边说的找方法方式一样,能找到流程为:- ReflectionMirrorImpl的impl 调用
subscript->childMetadata获取到fieldInfo获取 属性名 - 通过
childOffset函数和index获取到偏移量fieldOffset,最后根据内存偏移去到 属性值
- ReflectionMirrorImpl的impl 调用
总结
- 总的来说,整个流程都是通过
Metadata,getDescription(),FieldDescrition这⼏个东⻄来去实现的,⼀个是 当前类型的元数据,⼀个是 当前类型的描述,⼀个是 对当前类型属性的描述
结构图引用了: juejin.cn/post/705339… 的,相信对大家的理解会有帮助
归纳StructMetadata结构
struct StructMetaData{
var kind : Int32
//从TargetValueMetadata继承
var typeDescriptor : UnsafeMutablePointer<StructDescriptor>
}
struct StructDescriptor {
//Flags、Parent从TargetContextDescriptor继承
//描述上下文的标志,包括其Kind和Version
let flags: Int32
//父上下文,如果为顶级上下文,则为null
let parent: Int32
//结构体的名称
var name: RelativePointer<CChar>
var AccessFunctionPtr: RelativePointer<UnsafeRawPointer>
//记录属性内容
var Fields: RelativePointer<FieldDescriptor>
//结构中存储的属性数
var NumFields: Int32
//属性在元数据中字段偏移向量的偏移量
var FieldOffsetVectorOffset: Int32
}
//记录结构体内所有属性的结构
struct FieldDescriptor {
var MangledTypeName: RelativePointer<CChar>
var Superclass: RelativePointer<CChar>
var kind: UInt16
var fieldRecordSize: Int16
var numFields: Int32
//连续存储空间 (有几个数据,就会在后面添加几个记录,通过内存平移读取)
var fields: FieldRecord
}
struct FieldRecordT<Element> {
var element: Element
mutating func element(at i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
//每个属性的内容
struct FieldRecord {
var Flags: Int32
var MangledTypeName: RelativePointer<CChar>
var FieldName: RelativePointer<CChar>
}
// 相对位移指针,相当于RelativeIndirectPointer和RelativeDirectPointer
struct RelativePointer<T> {
var offset: Int32
// 偏移offset位置,获取内容指针
mutating func get() -> UnsafeMutablePointer<T>{
let offset = self.offset
//withUnsafePointer获取指针
// UnsafeMutablePointer 返回T类型对象的指针
// UnsafeRawPointer将p指针转换为未知类型
// advanced进行内存偏移
// numericCast将offset转换为偏移单位数
// assumingMemoryBound绑定指针为T类型
return withUnsafePointer(to: &self) { p in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}
解析自定义Struct
struct LZPerson {
var age: Int = 20
var name: String = "LZ"
}
//UnsafeMutablePointer<StructMetaData>.self 获取当前 struct 的 Metadata
//利⽤强转函数 unsafeBitCast 按位转换内存指针
//把 LZPerson Metadata 转成还原出来的 StructMetaData
let ptr = unsafeBitCast(LZPerson.self as Any.Type, to: UnsafeMutablePointer<StructMetaData>.self)
//通过属性内存的访问,获取到 name 字段的内存地址
let namePtr = ptr.pointee.typeDescriptor.pointee.name.get()
//经过 String 函数输出,得到我们结构体的名字
print("current class name: \(String(cString: namePtr))")
//获取属性的个数
let numFields = ptr.pointee.typeDescriptor.pointee.NumFields
print("当前类属性的个数: \(numFields)")
//获取属性的描述信息
let fieldDespritor = ptr.pointee.typeDescriptor.pointee.Fields.get()
//遍历属性
for i in 0..<numFields {
//获取属性的信息
let record = withUnsafePointer(to: &fieldDespritor.pointee.fields){
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: numericCast(i)))
}
//读取属性的名称
let recordNameStr = String(cString: record.pointee.FieldName.get())
//读取属性的类型
let manNameStr = String(cString: record.pointee.MangledTypeName.get())
print("类型名称:\(recordNameStr)---- 类型:\(manNameStr)")
}