Swift-Mirror源码解析
一、Runtime
1.什么是Runtime(运行时)
运行时刻是指一个程序在运行的状态。也就是说,当我们在打开一些程序在电脑上运行的时候,那个程序就是处于运行时刻。在一些编程语言中,把某些可以重用的程序或者实例打包或者重建成为“运行库"。这些实例可以在它们运行的时候被链接或者被任何程序调用。Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态的创建类和对象、进行消息传递和转发,而这些特性就是源于 Runtime。那么我们都知道Swift 是静态类型的语言,他有没有Runtime特性呢,接下来让我们深入探究一下。
1.2 用OC中的Runtime写法来获取类的属性
class LGTeacher{ var age = 18 } func test(){ var methodCount:UInt32 = 0 let methodlist = class_copyMethodList(LGTeacher.self, &methodCount) for i in 0..<numericCast(methodCount) { if let method = methodlist?[i]{ let methodName = method_getName(method); print("方法列表:\(String(describing: methodName))") }else{ print("not found method"); } } var count:UInt32 = 0 let proList = class_copyPropertyList(LGTeacher.self, &count) for i in 0..<numericCast(count) { if let property = proList?[i]{ let propertyName = property_getName(property); print("属性成员属性:\(String(utf8String: propertyName)!)") }else{
print("没有找到你要的属性"); } } } test()
运行后我们可以发现是没有任何输出的,即不具备Runtime属性,但是如果我们在类的属性前面加上@objc关键字,就发现获取到了属性
class LGTeacher{ @objc var age = 18 }如果类继承自NSObject呢?当类是继承自NSOject时,只有init方法会被会被执行
class LGTeacher:NSObject{ var age = 18 }
如果类继承自NSObject且属性前加上@objc呢?当同时满足上述的两个操作时,说明Swift通过Runtime的API获取到了属性和方法。
class LGTeacher:NSObject{ @objc var age = 18 }
通过以上的例子,我们可以发现:
- 对于纯 Swift 类来说,⽅法和属性在不加任何修饰符的情况下。这个时候其实已经没有Runtime 特性了。
- 对于纯 Swift 类,⽅法和属性添加@objc标识的情况下,当前我们可以通过 Runtime API拿到, 但是在 OC 中我们是没法进⾏调度(使用)的。
- 对于继承⾃ NSOject的类来说,如果我们想要动态的获取当前的属性和⽅法,必须在其声明前添加 @objc 关键字,否则也是没有办法通过 Runtime API 获取的。
- 纯swift类没有动态性,但在⽅法、属性前添加
dynamic
修饰,可获得动态性。 继承⾃NSObject的swift类,其继承⾃⽗类的⽅法具有动态性,其它⾃定义⽅法、属性想要获得动态性,需要添加dynamic修饰。 - 若⽅法的参数、属性类型为 swift特有、⽆法映射到 objective-c 的类型(如Character、Tuple),则 此⽅法、属性⽆法添加dynamic修饰(编译器报错)
二、Mirror的反射机制
上面我们提到Swift是没有Runtime特性的,那么在Swift中是如何获取到属性方法的呢,这就与我们现在说的Mirror反射息息相关了。
1.什么是Mirror反射
- 所谓
反射
就是可以动态获取类型、成员信息
,在运⾏时可以调⽤⽅法、属性等⾏为的特性。在使⽤OC开发时很少强调其反射概念,因为OC的Runtime要⽐其他语⾔中的反射强⼤的多
。但是 Swift 是⼀⻔类型安全
的语⾔,不⽀持我们像 OC 那样直接操作,它的标准库仍然提供了反射机制来让我们访问成员信息。 - Swift 的反射机制是基于⼀个叫Mirror的结构体来实现的。为具体的实例创建⼀个Mirror对象,然后就可以通过它查询这个实例。
2.Mirror的简单使用
class LGTeacher{ var age: Int = 18 } //首先通过构造方法构建一个Mirror实例,这里传入的参数是 Any,也就意味着当前可以是类,结构体,枚举等 let mirror = Mirror(reflecting: LGTeacher()) //接下来遍历 children 属性,这是一个集合 for pro in mirror.children{ //然后我们可以直接通过 label 输出当前的名称,value 输出当前反射的值 print("\(pro.label):\(pro.value)") }
假如我们不使用Mirror,此时就不会打印出具体的属性信息
class LGTeacher{ var age: Int = 18 } let t = LGTeacher()
我们根据刚刚上面创建的Mirror实例,去看看对应的定义是什么样子的
//常量类型是这个 public let children: Mirror.Children //Children是一个集合类型,参数是泛型 public typealias Children = AnyCollection<Mirror.Child> //泛型参数是一个元组类型
三、Mirror源码解读
首先,我们现在源文件中搜索Mirror.Swift文件,然后我们可以看到Mirror的结构体实现,定位到Mirror的初始化地方我们发现接收了一个Any类型的参数,同时使用if case来进行模式匹配,这里提到了一个CustomReflectable协议,我们在3.1中看看这个协议是怎么使用的,另外还有一个internalReflecting,我们在3.2中看看internalReflecting。
3.1 CustomReflectable的用法
class LGTeacher:CustomReflectable { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } var customMirror: Mirror{ let info = KeyValuePairs<String, Any>.init(dictionaryLiteral: ("age", age),("name", name)) let mirror = Mirror.init(self, children: info, displayStyle: .class, ancestorRepresentation: .generated) return mirror } }
我们发现当类遵守CustomReflectable协议时,就可以正常的打印属性。
3.2 internalReflecting
我们全局搜索internalReflecting,然后在ReflectionMirror.swift中发现了internalReflecting,如下所示:
在这里,我们发现了一个subjectType._getNormalizedType,再次经过全局搜索发现在ReflectionMirror.swift找到了,_getNormalizedType最终调用的是ReflectionMirror.cpp中的代码(在这里我们做一个补充说明:Swift的底层实现是C++,但是在Swift中又无法访问C++的类,因此会有一个C的连接层,反射在ReflectionMirror.swift和ReflectionMirror.cpp中实现,在cpp中呢会使用@_silgen_name,这其实是Swift的一个隐藏符号,作用是将某个C/C++语言函数直接映射为Swift函数。
接下来,我们看看call这个函数的具体实现,在call的具体实现中可以看到一个ReflectionMirrorImpl的调用,在调用这个结束之后会调用f从而实现反射工作,那么ReflectionMirrorImpl其实是一个抽象类,对于不同的类型的反射都需要去实现一个类似的Impl,我们在四中可以接着看看Struct的反射。
四、Struct的反射
首先我们看一下srtructImpl的实现:srtructImpl的结构体中存储一个可被访问的标志位,另外type是一个StructMetadata指针
struct StructImpl : ReflectionMirrorImpl { bool isReflectable() { const auto *Struct = static_cast<const StructMetadata *>(type); const auto &Description = Struct->getDescription(); return Description->isReflectable(); } }
- 结构体的子元素的数量是元数据给出的字段的数量,也有可能是0(0表示该类型实际上不能支持反射)
intptr_t count() override { if (!isReflectable()) { return 0; } auto *Struct = static_cast<const StructMetadata *>(type); return Struct->getDescription()->NumFields; }
- 结构体的显示样式
char displayStyle() override { return 's'; }
- subscript方法是比较复杂的部分,做边界检查和查找偏移值
intptr_t childOffset(intptr_t i) override { auto *Struct = static_cast<const StructMetadata *>(type); if (i < 0 || (size_t)i > Struct->getDescription()->NumFields) swift::crash("Swift mirror subscript bounds check failure"); // Load the offset from its respective vector. return Struct->getFieldOffsets()[i]; }
- 通过_swift_getFieldAt获取类型信息,一旦有字段信息,就会进行和srtructImpl对应的代码,填写名字和计算字段存储的指针
const FieldType childMetadata(intptr_t i, const char **outName, void (**outFreeFunc)(const char *)) override { StringRef name; FieldType fieldInfo; std::tie(name, fieldInfo) = getFieldAt(type, i); assert(!fieldInfo.isIndirect() && "indirect struct fields not implemented"); *outName = name.data(); *outFreeFunc = nullptr; return fieldInfo; }
4.1 读取struct源码
我们以struct为例来看一下Mirror是如何获取数据的,属性的数量可由Metadata中的getDescription查询字段NumFields
intptr_t count(){ if(!isReflectable()){ return 0 } auto *Struct = static_cast<const StructMetadata *>(type) return Struct->getDescription()->NumFields; }
const TargetStructDescriptor<Runtime> *getDescription() const { return llvm::cast<TargetStructDescriptor<Runtime>>(this->Description); }
- Metadata是当前类型的元数据
- getDescription是当前类型的描述
- FieldDescription是对当前类型属性的描述
4.2 Strcut的代码实现
在Metadata.h文件中,搜索TargetStructMetadata,然后通过定位,可以总结出一张流程图,看到对应的实现:
我们把上面的结构写下来:
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)) } } }
4.3 解析自定义的Struct
struct Animal { var age: Int = 2 var name: String = "pig" } //UnsafeMutablePointer<StructMetaData>.self 获取当前 struct 的 Metadata //利⽤强转函数 unsafeBitCast 按位转换内存指针 //把 Animal Metadata 转成还原出来的 StructMetaData let ptr = unsafeBitCast(Animal.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)") }
运行后就可以看到确实获取到了Struct名字、属性名称以及属性类型,Si是Int类型,SS代表String类型,这样就完成了Struct源码,其实Enum和结构体的Metadata数据结构差不多,而class的实现比较麻烦一些。
五、总结
- 获取当前struct的Metadata,然后转成StructMetadata
- 通过属性内存的访问,获取结构体的名称
- 获取属性的个数
- 获取属性的描述信息
- 读取属性的描述信息
六、元类型、AnyClass、Self(self)、AnyObject、Any
- T.self: T是实例对象,当前T.self返回的就是他本身;如果T是类,当前 T.self 返回的就是元类型(Metadata)
- Self: Self 类型不是特定类型,⽽是让您⽅便地引⽤当前类型,⽽⽆需重复或知道该类型的名称。 在协议声明或协议成员声明中,Self 类型是指最终符合协议的类型。可作为⽅法的返回类型, 作为只读下标的返回类型,作为只读计算属性的类型。
- AnyObject: 代表任意类实例,类类型,仅遵守类的协议
- Any: 代表任意类实例,包括 funcation 类型或者 Optional 类型,所有的类型都可以用Any表示, 包括基本数据类型, enum, struct, func(方法)
-
Any和AnyObject的区别
(1)AnyObject是Any的子集
(2)所有用class关键字定义的对象就是AnyObject
(3)所有不是用class关键字定义的对象就不是AnyObject,而是Any - AnyClass: 代表任意实例类型,AnyClass是AnyObject.Type的别名而已。
//AnyObject class LGTeacher{ var age = 18 } var t = LGTeacher() var t1: AnyObject = t//表示实例对象 var t2: AnyObject = LGTeacher.self//表示元类型(Metadata)
//T.self var t = LGTeacher() var t1 = t.self var t2 = t.self.self var t3 = LGTeacher.self
//self class LGTeacher{ var age = 18 func test(){ //当前实例对象 print(self) } static func test1(){ // self 是 LGTeacher 这个类型本身 print(self) } } var t = LGTeacher() t.test() LGTeacher.test1()
//LGSwiftTest.LGTeacher
//LGTeacher
//Self class LGTeacher{ static let age = 18 func test() -> Self{//作为返回值 //当前实例对象 return self } } class LGPerson { static let age = 0 let age1 = age var age2 = age lazy var age3 = Self.age } protocol MyProtocol { func get() -> Self }
//Any == id var array: [AnyObject] = [1, "Kody"] "X" var array2:[Any] = [1, "Kody",3,true]
如果用AnyObject是无法通过的,因为Int在Swift中是值类型,无法用AnyObject表示的
//AnyClass var t: AnyClass = LGTeacher.self// LGTeacher.Type
AnyClass = AnyObject.Type