Swift - Mirror 源码解析

·  阅读 107
Swift - Mirror 源码解析

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

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

image.png

这里可以看到,当协议继承 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

image.png

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

  • 在协议中使用

image.png

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

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

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

Any

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

image.png

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

AnyClass

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

image.png

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

class CXTeacher {
    var age = 18
}

var t: AnyClass = CXTeacher.self //这里 CXTeacher.self 就是属于 CXTeacher.Type 这个类型

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

type(of:)

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

image.png

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 标识再试一次。

image.png

这里可以看到,加上 @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)")
}

image.png

实际使用案例

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 信息,下面我们看一下:

image.png

image.png

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

image.png

image.png

如上图 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 方法。

image.png

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

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改