美文网首页Swift
Swift - Mirror 源码解析

Swift - Mirror 源码解析

作者: 晨曦的简书 | 来源:发表于2022-01-20 13:47 被阅读0次

    AnyObject、Any、AnyClass、Self 、self 介绍

    AnyObject

    • AnyObject:代表任意类的 instance(实例类型) 、类的类型 、仅类遵守的协议。
    class CXTeacher {
        var age = 18
    }
    
    var t = CXTeacher()
    // 代表当前的实例类型
    var t1: AnyObject = t
    
    // 代表类的类型 
    var t2: AnyObject = CXTeacher.self
    

    这里可以看到,当协议继承 AnyObject 的时候,该协议只能被类遵循。

    T.self

    • T.selfT 是实例对象,当前 T.self 返回的就是它的本身;如果 T 是类, T.self 返回的就是元类型。
    class CXTeacher {
        var age = 18
    }
    var t = CXTeacher()
    // t.self 是实例对象
    var t1 = t.self
    // t.self.self 也是实例对象
    var t2 = t.self.self
    // CXTeacher.self 是元类型,通过汇编代码也可以看到
    var t3 = CXTeacher.self
    

    self

    class CXTeacher {
        var age = 18
        
        func test() {
            // self 是当前实例对象
            print(self)
        }
        
        static func test2() {
            // self 是 CXTeacher 这个类型本身
            print(self)
        }
    }
    

    这里在实例方法 testself 代表当前实例对象,在类型方法 test2self 代表的是 CXTeacher 这个类型本身。

    Self

    • 作为返回值使用
    class CXTeacher {
        var age = 18
        
        func test() -> Self {
            return self
        }
        
        static func test2() {
            // self 是 CXTeacher 这个类型本身
            print(self)
        }
    }
    

    这里 Self 并没有别的特别的用意,只是为了让我们方便使用当前的类型,这里 Self 作为方法的返回类型,返回的是 self

    • 在协议中使用

    这里 Self 代表遵循此协议的类型。

    • 访问类型属性时使用
    class CXTeacher {
        static let age = 18
        
        lazy var age1 = Self.age
    }
    

    当我们定义一个类型属性,想访问该属性的时候可以用 Self.

    Any

    • Any: 代表任意类型,包括 funcation 类型或者 Optional 类型,Any 相对于 AnyObject 代表的范围更广泛一点。

    这里可以看到,array1 的时候会提示 1 不属于 AnyObject 类型。

    AnyClass

    • AnyClass:代表任意实例的类型。

    这里可以看到 AnyClassAnyObject.Type 类型。

    class CXTeacher {
        var age = 18
    }
    
    var t: AnyClass = CXTeacher.self //这里 CXTeacher.self 就是属于 CXTeacher.Type 这个类型
    

    这里 CXTeacher.self 就是属于 CXTeacher.Type 这个类型。

    type(of:)

    • type(of:):动态获取传入参数的实际类型。

    test 方法中,value 的静态类型是 Any,但是它的真实类型是 Int,可以通过 type(of:) 来动态获取,type(of:) 也可以理解为等同于 Self

    Swift Runtime

    class CXTeacher {
        var age: Int = 18
        func teach(){
            print("teach")
        }
    }
    
    let t = CXTeacher()
    
    func test(){
        var methodCount:UInt32 = 0
        let methodlist = class_copyMethodList(CXTeacher.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(CXTeacher.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()
    

    运行如上代码可以发现,当前不管是方法列表还是属性列表,此时都是空的。下面我们对属性跟方法加上 @objc 标识再试一次。

    这里可以看到,加上 @objc 标识之后属性列表跟方法列表可以正常输出,不过这个时候 CXTeacher 类中的方法跟属性对 OC 来说是不能使用的,想让 OC 使用的话需要让 CXTeacher 继承于 NSObject,不过继承于 NSObject 的类不加 @objc 的情况下,调用 test 方法属性列表跟方法列表也是为空。所以我们可以得出如下结论:

    • 对于纯 Swift 类来说,方法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的 Runtime 特性了,这和我们在前面讲的方法调度(V-Table调度)是不谋而合的。
    • 对于纯 Swift 类,方法和属性添加 @obic 标识的情况下,当前我们可以通过 Runtime API 拿到但是在我们的 OC 中是没法进行调度的。
    • 对于继承自 NSObject 的类来说,如果我们想要动态的获取当前的属性和方法,必须在其声明前添加 @objc 关键字,否则也是没有办法通过 RuntimeAPI 获取的。
    • swift 类没有动态性,但在方法、属性前添加 dynamic 修饰,可获得动态性。
    • 继承自 NSObiectswift 类,其继承自父类的方法具有动态性,其它自定义方法、属性想要获得动态性,需要添加 dynamic 修饰。
    • 若方法的参数、属性类型为 swift 特有、无法映射到 obiective-c 的类型如 CharacterTuple,则此方法、属性无法添加 dynamic修饰(编译器报错)。

    Mirror 的基本用法

    所谓反射就是可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。在使用 OC 开发时很少强调其反射概念,因为 OCRuntime 要比其他语言中的反射强大的多。但是 Swift 是一门类型安全的语言,不支持我们像 OC 那样直接操作,但它的标准库仍然提供了反射机制来让我们访问成员信息,Swift 的反射机制是基于一个叫 Mirror 的结构体来实现的。你为具体的实例创建一个 Mirror 对象,然后就可以通过它查询这个实例。

    • 用法介绍
    class CXTeacher {
        var age: Int = 18
    }
    
    //首先通过构造方法构建一个Mirror实例,这里传入的参参数是Any,也就意味着当前可以是类,结构体,枚举等
    let mirror = Mirror(reflecting:CXTeacher())
    //接下来遍历children属性,这是一个集合
    for pro in mirror.children{
        //然后我们可以直接通过label输出当前的名称 value 输出当前反射的值
        print("\(String(describing: pro.label)):\(pro.value)")
    }
    

    实际使用案例

    class CXTeacher {
        var age: Int = 18
    }
    
    func test(_ mirro0bj:Any)-> Any{
        let mirror=Mirror(reflecting: mirro0bj)
        //这里做一下判断,如果当前子属性为空,那么当前返回的就是 mirro0bj 自身
        guard !mirror.children.isEmpty else { return mirro0bj}
        
        var result:[String:Any] = [:]
        //这里是递归调用 test 方法,直到当前属性没有子属性了
        for child in mirror.children {
            if let key=child.label {
                result[key] = test(child.value)
            } else {
                print("NO Keys")
            }
        }
        return result
    }
    
    var result =  test(CXTeacher())
    print(result)
    

    这里我们封装一个 test 方法,将所有的属性包装到字典里面并返回。但是如果我们想让结构体、类、枚举、基础类型都具备这个方法,这个时候我们就可以通过协议来实现,示例代码如下:

    protocol JSONMap {
        func jsonMap() -> Any
    }
    
    extension JSONMap {
        func jsonMap() -> Any {
            let mirror = Mirror(reflecting: self)
            guard !mirror.children.isEmpty else {
                return self
            }
            var result:[String:Any] = [:]
            for child in mirror.children {
                if let value = child.value as? JSONMap {
                    if let key = child.label {
                        result[key] = value.jsonMap()
                    } else {
                        print("NO Keys")
                    }
                } else {
                     print("Value not conform JSONMap Protocol")
                }
            }
            return result
        }
    }
     
    class CXTeacher {
        var age: Int = 18
    }
     
    extension CXTeacher: JSONMap {}
    extension String: JSONMap {}
    extension Int: JSONMap {}
    print(CXTeacher().jsonMap())
    

    在上面代码中,我们的错误都是通过 print 输出来代替的,这样的话会显得很不专业,这里我们通过 Swift 中的错误处理来合理表达一个错误。

    Swift 提供 Error 协议来标识当前应用程序发生错误的情况,Error 的定义如下:

    public protocol Error : Sendable {}
    

    接下来我们把代码修改一下:

    enum JSONMapError: Error {
        case emptyKey
        case notConformProtocol
    }
    
    protocol JSONMap {
        func jsonMap() -> Any
    }
    
    extension JSONMap {
        func jsonMap() -> Any {
            let mirror = Mirror(reflecting: self)
            guard !mirror.children.isEmpty else {
                return self
            }
            var result:[String:Any] = [:]
            for child in mirror.children {
                if let value = child.value as? JSONMap {
                    if let key = child.label {
                        result[key] = value.jsonMap()
                    } else {
                        return JSONMapError.emptyKey
                    }
                } else {
                    return JSONMapError.notConformProtocol
                }
            }
            return result
        }
    }
    

    这里在错误的时候我们 return JSONMapError.emptyKeyreturn JSONMapError.notConformProtocol,直接 return 的话这里跟平常没啥区别,但是如果让方法的调用者意识到有错误发生呢?正确的方式是使用 throws 关键字。修改后的代码如下:

    enum JSONMapError: Error {
        case emptyKey
        case notConformProtocol
    }
    
    protocol JSONMap {
        func jsonMap() throws -> Any
    }
    
    extension JSONMap {
        func jsonMap() throws -> Any {
            let mirror = Mirror(reflecting: self)
            guard !mirror.children.isEmpty else {
                return self
            }
            var result:[String:Any] = [:]
            for child in mirror.children {
                if let value = child.value as? JSONMap {
                    if let key = child.label {
                        result[key] = try? value.jsonMap()
                    } else {
                        return JSONMapError.emptyKey
                    }
                } else {
                    return JSONMapError.notConformProtocol
                }
            }
            return result
        }
    }
     
    class CXTeacher {
        var age: Int = 18
    }
     
    extension CXTeacher: JSONMap {}
    extension String: JSONMap {}
    extension Int: JSONMap {}
    let t = CXTeacher()
    
    // 如果有错误就会向上抛出,可以结合 do {} catch {} 使用
    //try t.jsonMap()
    // 使用 try? 的话,代表返回的是一个可选类型,如果成功就会返回一个字典,如果错误就返回一个nil,错误不会向上传递
    //try? t.jsonMap()
    // 使用 try! 的话代表 t.jsonMap 必须有返回结果,不会发生错误,否则的话就会抛出异常
    //try! t.jsonMap()
    
    do {
        try t.jsonMap()
    } catch {
        print(error)
    }
    

    Mirror 源码解析

    首先我们在源文件里面搜索 Mirror.Swift, 在源码中我们可以很清晰的看到 Mirror 是由
    结构体实现的,我们忽略掉一些细节,现在我们快速定位到初始化的方法。

      public init(reflecting subject: Any) {
        if case let customized as CustomReflectable = subject {
          self = customized.customMirror
        } else {
          self = Mirror(internalReflecting: subject)
        }
      }
    

    这里可以看到,初始化方法中接收一个 Any 类型的参数,同样的这里有一个 if case 的写法来判断当前的 subject 是否遵循了 customReflectable 协议,如果是,我们就直接调用 customMirror,否则就进行下级函数的调用。

    这里有两个需要注意的是 if case 的写法,这里其实是枚举 Case 的模式匹配,和我们的 Switch 一样,这里是只有一个 caseswitch 语句。于此同时这里出现了一个 customRefletable 的协议。这里我们看一下 customRefletable 的具体用法:

    首先我们遵循 customReflectable 协议,并实现其中的属性 customMirrorcustomMirror 会返回一个 Mirror 对象。代码展示如下:

    class CXTeacher: 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 最直观的区别在与我们在 lldb debug 中会出现更详细的 debug 信息,下面我们看一下:

    在上面 Mirror 的初始化方法中当 subject 不遵循 customReflectable 协议的时候就会执行 self = Mirror(internalReflecting: subject) 这句代码,所以我们全局搜索 Mirror(internalReflecting,然后就可以定位到 ReflectionMirror .swift 文件。

    如上图 137 行这句代码是用来获取当前 subject 的类型,当然这个函数最终调用的是 C++ 的代码,这里使用了一个编译器字段 @ silgen_name 其实是 Swift 的一个隐藏符号,作用是将某个 C/C++语言函数直接映射为 Swift 函数。也可以理解为为 C++ 代码中的 swift_reflectionMirror_normalizedType 函数定义一个在 swift 中使用的别名 _getNormalizedType,所以 _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type 最终调用的是 ReflectionMirror.cpp 中的 C++ 代码。

    // func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type
    SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
    const Metadata *swift_reflectionMirror_normalizedType(OpaqueValue *value,
                                                          const Metadata *type,
                                                          const Metadata *T) {
      return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->type; });
    }
    

    swift_reflectionMirror_normalizedType 函数往上找就能够找到 call 函数的实现,这里其实是一个回调函数,当前回调的具体数据都是由 ReflectionMirrorImpl 结构体实现。

    auto call = [&](ReflectionMirrorImpl *impl) {
        impl->type = type;
        impl->value = value;
        auto result = f(impl);
        return result;
      };
    

    ReflectionMirrorImpl 结构体的具体实现(可以看到这是一个抽象类,也就意味着不同类型的
    反射都需要去实现 ReflectionMirrorImpl)这里我们在下面的代码中也能看到 class、struct、enum、Tuple 的具体实现。

    struct ReflectionMirrorImpl {
      const Metadata *type;
      OpaqueValue *value;
      
      virtual char displayStyle() = 0;
      virtual intptr_t count() = 0;
      virtual intptr_t childOffset(intptr_t index) = 0;
      virtual const FieldType childMetadata(intptr_t index,
                                            const char **outName,
                                            void (**outFreeFunc)(const char *)) = 0;
      virtual AnyReturn subscript(intptr_t index, const char **outName,
                                  void (**outFreeFunc)(const char *)) = 0;
      virtual const char *enumCaseName() { return nullptr; }
    
    #if SWIFT_OBJC_INTEROP
      virtual id quickLookObject() { return nil; }
    #endif
      
      // For class types, traverse through superclasses when providing field
      // information. The base implementations call through to their local-only
      // counterparts.
      virtual intptr_t recursiveCount() {
        return count();
      }
      virtual intptr_t recursiveChildOffset(intptr_t index) {
        return childOffset(index);
      }
      virtual const FieldType recursiveChildMetadata(intptr_t index,
                                                     const char **outName,
                                                     void (**outFreeFunc)(const char *))
      {
        return childMetadata(index, outName, outFreeFunc);
      }
    
      virtual ~ReflectionMirrorImpl() {}
    };
    

    这里我们以 enum 为例来看一下 Mirror 都是如何获取到这些数据的。

    struct EnumImpl : ReflectionMirrorImpl {
      bool isReflectable() {
        //将 EnumMetadata 进行类型强转
        const auto *Enum = static_cast<const EnumMetadata *>(type);
        //找到 Enum 的描述信息 getDescription
        const auto &Description = Enum->getDescription();
        //找到 Description 中的 isReflectable 字段,标识是否可以接收反射
        return Description->isReflectable();
      }
      
      const char *getInfo(unsigned *tagPtr = nullptr,
                          const Metadata **payloadTypePtr = nullptr,
                          bool *indirectPtr = nullptr) {
        // 'tag' is in the range [0..NumElements-1].
        unsigned tag = type->vw_getEnumTag(value);
    
        StringRef name;
        FieldType info;
        std::tie(name, info) = getFieldAt(type, tag);
        const Metadata *payloadType = info.getType();
        bool indirect = info.isIndirect();
    
        if (tagPtr)
          *tagPtr = tag;
        if (payloadTypePtr)
          *payloadTypePtr = payloadType;
        if (indirectPtr)
          *indirectPtr = indirect;
        
        return name.data();
      }
    
      char displayStyle() override {
        return 'e';
      }
      
      // 这里 count 是获取属性数量
      intptr_t count() override {
        if (!isReflectable()) {
          return 0;
        }
        
        // No fields if reflecting the enumeration type instead of a case
        if (!value) {
          return 0;
        }
    
        const Metadata *payloadType;
         (nullptr, &payloadType, nullptr);
        return (payloadType != nullptr) ? 1 : 0;
      }
    };
    

    EnumImpl 中我们可以看到通过 count() 获取属性数量的时候会调用 getInfo 方法,在 getInfo 方法中获取属性名称跟信息的时候会调用 getFieldAt 方法,所以我们继续追踪 getFieldAt 方法。

    可以看到是这里通篇都是通过 MetadatagetDescription()FieldDescrition 这几个东西来去实现的,一个是当前类型的元数据、一个是当前类型的描述、一个是对当前类型属性的
    描述。所以看到这里我们能够明白 Mirror 是如何工作的。

    相关文章

      网友评论

        本文标题:Swift - Mirror 源码解析

        本文链接:https://www.haomeiwen.com/subject/qkachrtx.html