Swift-Mirror源码解析

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
}
Swift-Mirror源码解析
如果类继承自NSObject呢?当类是继承自NSOject时,只有init方法会被会被执行
class LGTeacher:NSObject{
    var age = 18
}

Swift-Mirror源码解析

 

如果类继承自NSObject且属性前加上@objc呢?当同时满足上述的两个操作时,说明Swift通过Runtime的API获取到了属性和方法。

class LGTeacher:NSObject{
    @objc var age = 18
}

Swift-Mirror源码解析

 

通过以上的例子,我们可以发现:

  • 对于纯 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)")
}

Swift-Mirror源码解析

假如我们不使用Mirror,此时就不会打印出具体的属性信息 

class LGTeacher{
   var age: Int = 18
}
let t = LGTeacher()

Swift-Mirror源码解析

我们根据刚刚上面创建的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。

Swift-Mirror源码解析

 

 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
    }
}

Swift-Mirror源码解析

 

 

 我们发现当类遵守CustomReflectable协议时,就可以正常的打印属性。

3.2 internalReflecting

我们全局搜索internalReflecting,然后在ReflectionMirror.swift中发现了internalReflecting,如下所示:

Swift-Mirror源码解析

 在这里,我们发现了一个subjectType._getNormalizedType,再次经过全局搜索发现在ReflectionMirror.swift找到了,_getNormalizedType最终调用的是ReflectionMirror.cpp中的代码(在这里我们做一个补充说明:Swift的底层实现是C++,但是在Swift中又无法访问C++的类,因此会有一个C的连接层,反射在ReflectionMirror.swift和ReflectionMirror.cpp中实现,在cpp中呢会使用@_silgen_name,这其实是Swift的一个隐藏符号,作用是将某个C/C++语言函数直接映射为Swift函数。

Swift-Mirror源码解析

 接下来,我们看看call这个函数的具体实现,在call的具体实现中可以看到一个ReflectionMirrorImpl的调用,在调用这个结束之后会调用f从而实现反射工作,那么ReflectionMirrorImpl其实是一个抽象类,对于不同的类型的反射都需要去实现一个类似的Impl,我们在四中可以接着看看Struct的反射。

Swift-Mirror源码解析

 

 四、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,然后通过定位,可以总结出一张流程图,看到对应的实现:

Swift-Mirror源码解析

 

 我们把上面的结构写下来:

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的实现比较麻烦一些。

Swift-Mirror源码解析

 

五、总结

  • 获取当前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 
上一篇:Stream distinct去重案例


下一篇:Lc176-第二高的薪水