Swift 中的反射 Mirror

2021-11-26 14:13:57 浏览数 (1)

前言 Mirror是Swift中的反射机制,对于C#和Java开发人员来说,应该很熟悉反射这个概念。反射就是可以动态的获取类型以及成员信息,同时也可以在运行时动态的调用方法和属性等。

对于iOS开发人员来说,入门时使用的Objective-C是很少强调反射概念的,因为OC的Runtime要比其他语言的反射强大的多。

1. Mirror 简介

MirrorSwift中的反射机制的实现,它的本质是一个结构体。其部分源码(Swift 5.3.1)如下:

代码语言:javascript复制
public struct Mirror {

  /// A suggestion of how a mirror's subject is to be interpreted.
  ///
  /// Playgrounds and the debugger will show a representation similar
  /// to the one used for instances of the kind indicated by the
  /// `DisplayStyle` case name when the mirror is used for display.
  public enum DisplayStyle {
    case `struct`, `class`, `enum`, tuple, optional, collection
    case dictionary, `set`
  }
    
    /// The static type of the subject being reflected.
    ///
    /// This type may differ from the subject's dynamic type when this mirror
    /// is the `superclassMirror` of another mirror.
    public let subjectType: Any.Type

    /// A collection of `Child` elements describing the structure of the
    /// reflected subject.
    public let children: Children

    /// A suggested display style for the reflected subject.
    public let displayStyle: DisplayStyle?

    /// A mirror of the subject's superclass, if one exists.
    public var superclassMirror: Mirror? {
      return _makeSuperclassMirror()
    }
}
  • subjectType:表示类型,被反射主体的类型
  • children:子元素集合
  • displayStyle:显示类型,基本类型为 nil 枚举值: struct, class, enum, tuple, optional, collection, dictionary, set
  • superclassMirror:父类反射, 没有父类为 nil

除了这些属性还有一些初始化方法,我们最常用的就是初始化方法就是:

代码语言:javascript复制
  /// Creates a mirror that reflects on the given instance.
  ///
  /// If the dynamic type of `subject` conforms to `CustomReflectable`, the
  /// resulting mirror is determined by its `customMirror` property.
  /// Otherwise, the result is generated by the language.
  ///
  /// If the dynamic type of `subject` has value semantics, subsequent
  /// mutations of `subject` will not observable in `Mirror`.  In general,
  /// though, the observability of mutations is unspecified.
  ///
  /// - Parameter subject: The instance for which to create a mirror.
  public init(reflecting subject: Any) {
    if case let customized as CustomReflectable = subject {
      self = customized.customMirror
    } else {
      self = Mirror(internalReflecting: subject)
    }
  }

根据源码我们还可以看到能够使用customMirror,这个没太研究,在源码中可以看到很多CustomMirror的身影,感兴趣的可以去研究研究。对于非customMirror的统一使用Mirror(internalReflecting:)进行初始化。

关于customMirror的补充,摘抄自 swiftGG Mirror 的工作原理。

Mirror允许类型用遵循 CustomReflectable 协议的方式提供一个自定义的表示方式。这给那些想表示得比内建形式更友好的类型提供一种有效的方法。比如 Array 类型遵守 CustomReflectable 协议并且暴露其中的元素为无标签的 Children。Dictionary 使用这种方法暴露其中的键值对为带标签的 Children。

2. Mirror的简单使用

2.1 基本使用

这里我们通过使用Mirror打印对象的属性名称和属性值。

代码语言:javascript复制
class Person {
    var name: String = "xiaohei"
    var age: Int = 18
    var height = 1.85
}

var p = Person()
var mirror = Mirror(reflecting: p.self)

print("对象类型:(mirror.subjectType)")
print("对象属性个数:(mirror.children.count)")
print("对象的属性及属性值")
for child in mirror.children {
    print("(child.label!)---(child.value)")
}

打印结果:

我们可以看到,属性名称和值都已经正常打印。

2.2 将对象转换为字典

首先我们来体验一下将对象转换为字典。

代码语言:javascript复制
class Animal {
    var name: String?
    var color: String?
    private var birthday: Date = Date(timeIntervalSince1970: 0)
}
 
class Cat: Animal {
    var master = "小黑"
    var like: [String] = ["mouse", "fish"]
    
    override init() {
        super.init()
        color = "黄色"
    }
}

func mapDic(mirror: Mirror) -> [String: Any] {
    var dic: [String: Any] = [:]
    for child in mirror.children {
        // 如果没有labe就会被抛弃
        if let label = child.label {
            let propertyMirror = Mirror(reflecting: child.value)
            print(propertyMirror)
            dic[label] = child.value
        }
    }
    // 添加父类属性
    if let superMirror = mirror.superclassMirror {
        let superDic = mapDic(mirror: superMirror)
        for p in superDic {
            dic[p.key] = p.value
        }
    }
    return dic
}


// Mirror使用
let cat = Cat()
cat.name = "大橘为重"
let mirror = Mirror(reflecting: cat)
let mirrorDic = mapDic(mirror: mirror)
print(mirrorDic)

打印结果:

通过打印结果我们可以看到,对于一些基本类型,已经可选类型的数据都已经转换为字典值,对于私有属性也可以完成转换。如果里面包含类则还需要进行递归处理。

2.3 转 JSON

注:这里并没有真正的转换成json字符串,还是只转换成了字典,重要在思想,如果需要转换成json还需要很多优化,以及特殊字符串的考量。

其实提到反射我们想到最多的应该就是JSON了,这里我们利用Mirror的特性,将对象转换成字典,对基本类型和类做了相应的处理,体会一下转json的思路。

首先我们定义一个Person对象,代码如下:

代码语言:javascript复制
struct Person {
    var name: String = "xiaohei"
    var age: Int = 18
    var isMale: Bool = true
    var address: Address? = Address(street: "xizhimen North")
    var height = 1.85
    var like: Array = ["eat", "sleep", "play"]
    var weight: Float = 75.0
    var some: Int?
}

struct Address {
    var street: String
}

// 创建一个Person对象
let p = Person()

为了通用性,我们可以编写一个协议,用来为所有类型提供转换的方法,只需要遵守该协议就可以使用协议中的方法。

代码语言:javascript复制
protocol CustomJSONProtocol {
    func toJSON() throws -> Any?
}

协议的实现过程中会有些错误,我们也简单的定义个枚举,方便处理。为了更加详细的描述错误信息,我们添加了错误描述和错误 code。

代码语言:javascript复制
// 转 json 时的错误类型
enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

// 错误描述
extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
        case .emptyKey:
            return "key 为空"
        case .notConformProtocol:
           return "没遵守协议"
        }
    }
}

// errorcode
extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 100
        case .notConformProtocol:
            return 101
        }
    }
}

协议实现的代码:

代码语言:javascript复制
extension CustomJSONProtocol {
    func toJSON() throws -> Any? {
        
        //创建 Mirror 类型
        let mirror = Mirror(reflecting: self)
        // 如果没有属性,比如一般类型String、Int等,直接返回自己
        guard !mirror.children.isEmpty else { return self }
        
        var result: [String:Any] = [:]
        // 遍历
        for children in mirror.children {
            if let value = children.value as? CustomJSONProtocol{
                if let key = children.label {
                    print(key)
                    result[key] = try value.toJSON()
                } else {
                   throw JSONMapError.emptyKey
                }
            } else {
                  throw JSONMapError.notConformProtocol
            }
        }
        
        return result
    }
}

将用到的类型都遵守协议

代码语言:javascript复制
//将一般类型都遵从 CustomJSONProtocol 协议
extension Person: CustomJSONProtocol {}
extension String: CustomJSONProtocol {}
extension Int: CustomJSONProtocol {}
extension Bool: CustomJSONProtocol {}
extension Double: CustomJSONProtocol {}
extension Float: CustomJSONProtocol {}
    
extension Address: CustomJSONProtocol {}

// 数组需要单独处理,要不然就会报错emptyKey
extension Array: CustomJSONProtocol {
    func toJSON() throws -> Any? {
        return self
    }
}

//Optionai 需要特别对待,原因是如果直接返回,则会是 .Some: [...]
extension Optional: CustomJSONProtocol {
    func toJSON() throws -> Any? {
        if let x = self {
            if let value = x as? CustomJSONProtocol {
                return try value.toJSON()
            }
            throw JSONMapError.notConformProtocol
        }
        return nil
    }
}

最后我们打印一下:

代码语言:javascript复制
do {
    print(try p.toJSON()!)
} catch {
    print(error.localizedDescription)
    print((error as? JSONMapError)?.errorCode)
}

打印结果:

我们看到,对于some这空值,并没有存储到字典中,因为swift中的字典对于空值是删除的意思。

如果想将其转换成json还需修改"[]"为"{}",这个对于数组和对象还不好区分,另外对于json字符串内的一些value也有可能是应一串json还需要添加转义字符等。

所以总的来说,思路是这样的,要想真正的做成通用的转json的方案还需要很多的优化,比如说,我们不可能将所有的基本类型都去遵守一个协议,这时候我们也可以考虑使用泛型去作为方法的参数。

3. Mirror 源码解析 源码版本Swift 5.3.1

在本章节我们将分析Mirror的部分源码,查看其底层实现,最后通过Swift代码使用内存重绑定的形式,仿写一下Mirror,来更好的探索Mirror的原理,理解Mirror的思想。

我们知道Swift是一门静态语言,那么在底层是如何实现的获取对应的属性值的呢?又或者说Swift的反射特性是如何实现的呢?下面我们通过对Mirror底层源码的探索来寻找答案。

▐ 3.1 代码结构

Mirror的实现是由一部分Swift代码加上另一部分C 代码。Swift代码实现在ReflectionMirror.swift文件中,C 代码实现在ReflectionMirror.mm文件中。Swift更适合用在实现更Swift的接口,但是在Swift中不能直接访问C 的类。这里使用了@_silgen_name来实现Swift调用C 中的方法。举个例子:

代码语言:javascript复制
@_silgen_name("swift_reflectionMirror_count")
internal func _getChildCount<T>(_: T, type: Any.Type) -> Int

@_silgen_name修饰符会通知Swift编译器将这个函数映射成swift_reflectionMirror_count符号,而不是Swift通常对应到的_getChildCount方法名修饰。需要注意的是,最前面的下划线表示这个修饰是被保留在标准库中的。在C 这边,这个函数是这样的。

代码语言:javascript复制
// func _getChildCount<T>(_: T, type: Any.Type) -> Int
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
intptr_t swift_reflectionMirror_count(OpaqueValue *value,
                                      const Metadata *type,
                                      const Metadata *T) {
  return call(value, T, type, [](ReflectionMirrorImpl *impl) {
    return impl->count();
  });
}

SWIFT_CC(swift)会告诉编译器这个函数使用的是Swift的调用约定,而不是C/C 的,SWIFT_RUNTIME_STDLIB_API标记这个函数,在Swift侧的一部分接口中,而且它还有标记为extern "C"的作用,从而避免C 的方法名修饰,并确保它在Swift侧会有预期的符号。同时C 的参数会去特意匹配在Swift中声明的函数调用。当Swift调用_getChildCount时,C 会用包含Swift值指针的value,包含类型参数type,包含类型响应的泛型<T>T的函数参数来调用此函数。

简单的说就是使用@_silgen_name("xxx")修饰符修饰的Swift方法会调用括号中的xxx的符号,不管是C 的还是C的都可以。

Mirror的在SwiftC 之间的全部接口由以下函数组成:

代码语言:javascript复制
@_silgen_name("swift_isClassType")
internal func _isClassType(_: Any.Type) -> Bool

@_silgen_name("swift_getMetadataKind")
internal func _metadataKind(_: Any.Type) -> UInt

@_silgen_name("swift_reflectionMirror_normalizedType")
internal func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type

@_silgen_name("swift_reflectionMirror_count")
internal func _getChildCount<T>(_: T, type: Any.Type) -> Int

@_silgen_name("swift_reflectionMirror_recursiveCount")
internal func _getRecursiveChildCount(_: Any.Type) -> Int

@_silgen_name("swift_reflectionMirror_recursiveChildMetadata")
internal func _getChildMetadata(
  _: Any.Type,
  index: Int,
  outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
  outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
) -> Any.Type

@_silgen_name("swift_reflectionMirror_recursiveChildOffset")
internal func _getChildOffset(
  _: Any.Type,
  index: Int
) -> Int

internal typealias NameFreeFunc = @convention(c) (UnsafePointer<CChar>?) -> Void

@_silgen_name("swift_reflectionMirror_subscript")
internal func _getChild<T>(
  of: T,
  type: Any.Type,
  index: Int,
  outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
  outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
) -> Any

// Returns 'c' (class), 'e' (enum), 's' (struct), 't' (tuple), or '' (none)
@_silgen_name("swift_reflectionMirror_displayStyle")
internal func _getDisplayStyle<T>(_: T) -> CChar

internal func getChild<T>(of value: T, type: Any.Type, index: Int) -> (label: String?, value: Any) {
  var nameC: UnsafePointer<CChar>? = nil
  var freeFunc: NameFreeFunc? = nil
  
  let value = _getChild(of: value, type: type, index: index, outName: &nameC, outFreeFunc: &freeFunc)
  
  let name = nameC.flatMap({ String(validatingUTF8: $0) })
  freeFunc?(nameC)
  return (name, value)
}

#if _runtime(_ObjC)
@_silgen_name("swift_reflectionMirror_quickLookObject")
internal func _getQuickLookObject<T>(_: T) -> AnyObject?

@_silgen_name("_swift_stdlib_NSObject_isKindOfClass")
internal func _isImpl(_ object: AnyObject, kindOf: UnsafePointer<CChar>) -> Bool
3.2 初始化

在一开始我们简单的介绍了Mirror的部分源码,也由此知道Mirror(reflecting:)初始化方法可以接受任意值,返回一个提供该值子元素集合Children的相关信息的实例。

通过Mirror(reflecting:)源码我们可以知道,其底层调用的是internalReflecting方法。在ReflectionMirror.swift文件的extension Mirror中我们可以找到该方法。其源码如下:

3.2.1 internalReflecting

代码语言:javascript复制
 internal init(internalReflecting subject: Any,
              subjectType: Any.Type? = nil,
              customAncestor: Mirror? = nil)
  {
    let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))
    
    let childCount = _getChildCount(subject, type: subjectType)
    let children = (0 ..< childCount).lazy.map({
      getChild(of: subject, type: subjectType, index: $0)
    })
    self.children = Children(children)
    
    self._makeSuperclassMirror = {
      guard let subjectClass = subjectType as? AnyClass,
            let superclass = _getSuperclass(subjectClass) else {
        return nil
      }
      
      // Handle custom ancestors. If we've hit the custom ancestor's subject type,
      // or descendants are suppressed, return it. Otherwise continue reflecting.
      if let customAncestor = customAncestor {
        if superclass == customAncestor.subjectType {
          return customAncestor
        }
        if customAncestor._defaultDescendantRepresentation == .suppressed {
          return customAncestor
        }
      }
      return Mirror(internalReflecting: subject,
                    subjectType: superclass,
                    customAncestor: customAncestor)
    }
    
    let rawDisplayStyle = _getDisplayStyle(subject)
    switch UnicodeScalar(Int(rawDisplayStyle)) {
    case "c": self.displayStyle = .class
    case "e": self.displayStyle = .enum
    case "s": self.displayStyle = .struct
    case "t": self.displayStyle = .tuple
    case "": self.displayStyle = nil
    default: preconditionFailure("Unknown raw display style '(rawDisplayStyle)'")
    }
    
    self.subjectType = subjectType
    self._defaultDescendantRepresentation = .generated
  }

源码分析:

  • 首先是获取subjectType,如果传入的有值就使用传入的值,否则就通过_getNormalizedType函数去获取
  • 接下来就是通过_getChildCount获取childCount
  • 接下来是children,注意这里是懒加载的
  • 紧接着是SuperclassMirror,这里使用的是一个闭包的形式
  • 最后会获取并解析显示的样式,并设置Mirror剩下的属性。

3.2.2 _getNormalizedType

下面我们就来看看_getNormalizedType函数内部的实现。根据上面的分析我们知道实际调用是swift_reflectionMirror_normalizedType。在ReflectionMirror.mm文件中我们可以看到其源码:

代码语言:javascript复制
// 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; });
}

3.2.3 call函数

我们可以看到这里调用了一个call函数,最后返回的是impltype。首先我们看看这call函数:

代码语言:javascript复制
template<typename F>
auto call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType,
          const F &f) -> decltype(f(nullptr))
{
  const Metadata *type;
  OpaqueValue *value;
  std::tie(type, value) = unwrapExistential(T, passedValue);
  
  if (passedType != nullptr) {
    type = passedType;
  }
  
  auto call = [&](ReflectionMirrorImpl *impl) {
    impl->type = type;
    impl->value = value;
    auto result = f(impl);
    return result;
  };
  
  auto callClass = [&] {
    if (passedType == nullptr) {
      // Get the runtime type of the object.
      const void *obj = *reinterpret_cast<const void * const *>(value);
      auto isa = _swift_getClass(obj);

      // Look through artificial subclasses.
      while (isa->isTypeMetadata() && isa->isArtificialSubclass()) {
        isa = isa->Superclass;
      }
      passedType = isa;
    }

  #if SWIFT_OBJC_INTEROP
    // If this is a pure ObjC class, reflect it using ObjC's runtime facilities.
    // ForeignClass (e.g. CF classes) manifests as a NULL class object.
    auto *classObject = passedType->getClassObject();
    if (classObject == nullptr || !classObject->isTypeMetadata()) {
      ObjCClassImpl impl;
      return call(&impl);
    }
  #endif

    // Otherwise, use the native Swift facilities.
    ClassImpl impl;
    return call(&impl);
  };
  
  switch (type->getKind()) {
    case MetadataKind::Tuple: {
      TupleImpl impl;
      return call(&impl);
    }

    case MetadataKind::Struct: {
      StructImpl impl;
      return call(&impl);
    }
    

    case MetadataKind::Enum:
    case MetadataKind::Optional: {
      EnumImpl impl;
      return call(&impl);
    }
      
    case MetadataKind::ObjCClassWrapper:
    case MetadataKind::ForeignClass:
    case MetadataKind::Class: {
      return callClass();
    }

    case MetadataKind::Metatype:
    case MetadataKind::ExistentialMetatype: {
      MetatypeImpl impl;
      return call(&impl);
    }

    case MetadataKind::Opaque: {
#if SWIFT_OBJC_INTEROP
      // If this is the AnyObject type, use the dynamic type of the
      // object reference.
      if (type == &METADATA_SYM(BO).base) {
        return callClass();
      }
#endif
      // If this is the Builtin.NativeObject type, and the heap object is a
      // class instance, use the dynamic type of the object reference.
      if (type == &METADATA_SYM(Bo).base) {
        const HeapObject *obj
          = *reinterpret_cast<const HeapObject * const*>(value);
        if (obj->metadata->getKind() == MetadataKind::Class) {
          return callClass();
        }
      }
      LLVM_FALLTHROUGH;
    }

    /// TODO: Implement specialized mirror witnesses for all kinds.
    default:
      break;

    // Types can't have these kinds.
    case MetadataKind::HeapLocalVariable:
    case MetadataKind::HeapGenericLocalVariable:
    case MetadataKind::ErrorObject:
      swift::crash("Swift mirror lookup failure");
    }

    // If we have an unknown kind of type, or a type without special handling,
    // treat it as opaque.
    OpaqueImpl impl;
    return call(&impl);
}

乍一看这个call函数代码相对有点多,其实主要就是一个大型的switch声明,和一些对特殊情况的处理,这里重要的就是,它会用一个ReflectionMirrorImpl的子类实例去结束调用f,然后会调用这个实例上的方法去让真正的工作完成,这也就是为什么在swift_reflectionMirror_normalizedType函数中最后会return impl->type;感兴趣的可以通过源码去调试一下,这里我也调试了,没什么指的说的,这就就不写调试过程了,下面我们就来看看ReflectionMirrorImpl

3.2.4 ReflectionMirrorImpl

ReflectionMirrorImpl有以下6个子类:

  • TupleImpl 元组的反射
  • StructImpl 结构体的反射
  • EnumImpl 枚举的反射
  • ClassImpl 类的反射
  • MetatypeImpl 元数据的反射
  • OpaqueImpl 不透明类型的反射

ReflectionMirrorImpl 源码:

代码语言:javascript复制
// Abstract base class for reflection implementations.
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() {}
};

ReflectionMirrorImpl源码不多,但是我们可以看到type以及count等都在其中。下面我们以结构体为例,看看其子类的源码。

3.2.5 结构体的反射

代码语言:javascript复制
// Implementation for structs.
struct StructImpl : ReflectionMirrorImpl {
  bool isReflectable() {
    const auto *Struct = static_cast<const StructMetadata *>(type);
    const auto &Description = Struct->getDescription();
    return Description->isReflectable();
  }

  char displayStyle() {
    return 's';
  }
  
  intptr_t count() {
    if (!isReflectable()) {
      return 0;
    }

    auto *Struct = static_cast<const StructMetadata *>(type);
    return Struct->getDescription()->NumFields;
  }

  intptr_t childOffset(intptr_t i) {
    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];
  }

  const FieldType childMetadata(intptr_t i, const char **outName,
                                void (**outFreeFunc)(const char *)) {
    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;
  }

  AnyReturn subscript(intptr_t i, const char **outName,
                      void (**outFreeFunc)(const char *)) {
    auto fieldInfo = childMetadata(i, outName, outFreeFunc);

    auto *bytes = reinterpret_cast<char*>(value);
    auto fieldOffset = childOffset(i);
    auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes   fieldOffset);

    return copyFieldContents(fieldData, fieldInfo);
  }
};
  • 首先一个判断是否支持反射的方法,最中是访问的Description->isReflectable()
  • 这里使用‘s’来显式的表明这是一个结构体
  • 然后是获取属性个数
  • 紧接着是获取每个属性的偏移值
  • 然后获取了FieldType内部还可以获取到属性的名称。
  • 最后subscript方法可以获取到属性名称和属性偏移的指针,也就是属性值。
3.3 Description

在此处我们看到很多关于Description的代码,看来这个Description存储着很多信息,在获取Description的时候是从StructMetadata通过getDescription()方法获取到。所以这些信息基本确定是从MetaData中获取到的。StructMetadata是TargetStructMetadata的别名,我们以此为例。

3.3.1 TargetStructMetadata

TargetStructMetadata我们可以看到如下代码:

代码语言:javascript复制
const TargetStructDescriptor<Runtime> *getDescription() const {
    return llvm::cast<TargetStructDescriptor<Runtime>>(this->Description);
}

说明此处会返回一个TargetStructDescriptor类型的Description,但是在这里并没有找到这个属性,可能在父类中,我们可以看到TargetStructMetadata继承自TargetValueMetadata

3.3.2 TargetValueMetadata

在这里我们可以看到如下代码:

代码语言:javascript复制
/// An out-of-line description of the type.
TargetSignedPointer<Runtime, const TargetValueTypeDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
  
getDescription() const {
    return Description;
}

这里我们便找到了:

  • Description属性,它的类型是TargetValueTypeDescriptor,应该是TargetStructDescriptor的父类。
  • getDescription()方法,在TargetStructMetadata是重写的这个方法

3.3.3 TargetStructDescriptor

跳转到TargetStructDescriptor中后,我们可以看到:

  • TargetValueTypeDescriptor果然是它的父类
  • 在其源码中我们还可以发现两个属性,分别是NumFieldsFieldOffsetVectorOffset源码如下:
代码语言:javascript复制
/// The number of stored properties in the struct.
  /// If there is a field offset vector, this is its length.
  uint32_t NumFields;
  /// The offset of the field offset vector for this struct's stored
  /// properties in its metadata, if any. 0 means there is no field offset
  /// vector.
  uint32_t FieldOffsetVectorOffset;
  • 其中NumFields主要表示结构体中属性的个数,如果只有一个字段偏移量则表示偏移量的长度
  • FieldOffsetVectorOffset表示这个结构体元数据中存储的属性的字段偏移向量的偏移量,如果是0则表示没有

3.3.4 TargetValueTypeDescriptor

源码如下,很少:

代码语言:javascript复制
template <typename Runtime>
class TargetValueTypeDescriptor
    : public TargetTypeContextDescriptor<Runtime> {
public:
  static bool classof(const TargetContextDescriptor<Runtime> *cd) {
    return cd->getKind() == ContextDescriptorKind::Struct ||
           cd->getKind() == ContextDescriptorKind::Enum;
  }
};

在这里我们并没有找到太多有用的信息,我们继续向父类寻找,其继承自TargetTypeContextDescriptor

3.3.5 TargetTypeContextDescriptor

部分源码:

代码语言:javascript复制
template <typename Runtime>
class TargetTypeContextDescriptor
    : public TargetContextDescriptor<Runtime> {
public:
  /// The name of the type.
  TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;

  /// A pointer to the metadata access function for this type.
  ///
  /// The function type here is a stand-in. You should use getAccessFunction()
  /// to wrap the function pointer in an accessor that uses the proper calling
  /// convention for a given number of arguments.
  TargetRelativeDirectPointer<Runtime, MetadataResponse(...),
                              /*Nullable*/ true> AccessFunctionPtr;
  
  /// A pointer to the field descriptor for the type, if any.
  TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
                              /*nullable*/ true> Fields;
}

我们可以看到:

  • 该类继承自TargetContextDescriptor
  • NameAccessFunctionPtrFields三个属性
    • 其中name就是类型的名称
    • AccessFunctionPtr是该类型元数据访问函数的指针
    • Fields是一个指向该类型的字段描述符的指针

3.3.6 TargetContextDescriptor

接下来我们再看看TargetTypeContextDescriptor的父类中还有什么有用的信息。部分源码如下:

代码语言:javascript复制
/// Base class for all context descriptors.
template<typename Runtime>
struct TargetContextDescriptor {
  /// Flags describing the context, including its kind and format version.
  ContextDescriptorFlags Flags;
  
  /// The parent context, or null if this is a top-level context.
  TargetRelativeContextPointer<Runtime> Parent;
}

这里我们可以看到:

  • 这就是descriptors的基类
  • 有两个属性,分别是FlagsParent
    • 其中Flags是描述上下文的标志,包括它的种类和格式版本。
    • Parent是记录父类上下文的,如果是顶级则为null

3.3.7 小结

至此我们对结构体Description的层级结构基本就理清楚了,现总结如下:

从上图我们可以看到,对于一个结构体的Description来说,继承链上一共四个类,7个属性。下面我们就对这些属性作进一步的分析

3.4 Description中的属性

3.4.1 Flags

Flags的类型是ContextDescriptorFlags,我们点击跳转进去,我们可以看到:

代码语言:javascript复制
/// Common flags stored in the first 32-bit word of any context descriptor.
struct ContextDescriptorFlags {
private:
  uint32_t Value;

  explicit constexpr ContextDescriptorFlags(uint32_t Value)
    : Value(Value) {}
public:
  constexpr ContextDescriptorFlags() : Value(0) {}
  constexpr ContextDescriptorFlags(ContextDescriptorKind kind,
                                   bool isGeneric,
                                   bool isUnique,
                                   uint8_t version,
                                   uint16_t kindSpecificFlags)
    : ContextDescriptorFlags(ContextDescriptorFlags()
                               .withKind(kind)
                               .withGeneric(isGeneric)
                               .withUnique(isUnique)
                               .withVersion(version)
                               .withKindSpecificFlags(kindSpecificFlags))
  {}
  
  ......
}

从以上的代码中我们可以看到这个FLags实际是个uint32_t的值,按位存储着kindisGenericisUniqueversion等信息。

3.4.2 Parent

Parent的类型是TargetRelativeContextPointer<Runtime>,我们看看TargetRelativeContextPointer,点击跳转过去:

代码语言:javascript复制
using TargetRelativeContextPointer =
  RelativeIndirectablePointer<const Context<Runtime>,
                              /*nullable*/ true, int32_t,
                              TargetSignedContextPointer<Runtime, Context>>;

我们可以看到TargetRelativeContextPointer是取自RelativeIndirectablePointer的别名,继续点击进行查看:

代码语言:javascript复制
/// A relative reference to an object stored in memory. The reference may be
/// direct or indirect, and uses the low bit of the (assumed at least
/// 2-byte-aligned) pointer to differentiate.
template<typename ValueTy, bool Nullable = false, typename Offset = int32_t, typename IndirectType = const ValueTy *>
class RelativeIndirectablePointer {

    /// The relative offset of the pointer's memory from the `this` pointer.
    /// If the low bit is clear, this is a direct reference; otherwise, it is
    /// an indirect reference.
    Offset RelativeOffsetPlusIndirect;
  
        const ValueTy *get() const & {
        static_assert(alignof(ValueTy) >= 2 && alignof(Offset) >= 2,
                      "alignment of value and offset must be at least 2 to "
                      "make room for indirectable flag");
      
        // Check for null.
        if (Nullable && RelativeOffsetPlusIndirect == 0)
          return nullptr;
        
        Offset offsetPlusIndirect = RelativeOffsetPlusIndirect;
        uintptr_t address = detail::applyRelativeOffset(this,
                                                        offsetPlusIndirect & ~1);
    
        // If the low bit is set, then this is an indirect address. Otherwise,
        // it's direct.
        if (offsetPlusIndirect & 1) {
          return *reinterpret_cast<IndirectType const *>(address);
        } else {
          return reinterpret_cast<const ValueTy *>(address);
        }   
    }
}

根据注释我们就可以轻松的知道这个类的主要作用是存储在内存中的对象的相对引用。这个意思在后面也有相关的解释就是在内存中引用可以是直接的也可以是间接的,直接的就是存储的绝对地址(也不一定,还有ASLR等),直接访问这个地址就可以拿到对应的数据,而这里的相对引用就是间接的,这里面通过RelativeOffsetPlusIndirect属性存储相对的地址偏移量,在通过get()函数获取,在get()函数中,会调用applyRelativeOffset函数,进行地址的偏移,applyRelativeOffset源码:

代码语言:javascript复制
/// Apply a relative offset to a base pointer. The offset is applied to the base
/// pointer using sign-extended, wrapping arithmetic.
template<typename BasePtrTy, typename Offset>
static inline uintptr_t applyRelativeOffset(BasePtrTy *basePtr, Offset offset) {
  static_assert(std::is_integral<Offset>::value &&
                std::is_signed<Offset>::value,
                "offset type should be signed integer");

  auto base = reinterpret_cast<uintptr_t>(basePtr);
  // We want to do wrapping arithmetic, but with a sign-extended
  // offset. To do this in C, we need to do signed promotion to get
  // the sign extension, but we need to perform arithmetic on unsigned values,
  // since signed overflow is undefined behavior.
  auto extendOffset = (uintptr_t)(intptr_t)offset;
  return base   extendOffset;
}

最后返回的时候我们可以看到base extendOffset;基地址加上偏移的值,最后得到真实的地址。

3.4.2 name

name的类型是TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false>,看着应该和Parent差不多。我们点击TargetRelativeDirectPointer跳转到它的源码处:

代码语言:javascript复制
template <typename Runtime, typename Pointee, bool Nullable = true>
using TargetRelativeDirectPointer
  = typename Runtime::template RelativeDirectPointer<Pointee, Nullable>;

这里依旧是取别名,继续跳转到RelativeDirectPointer,这里有两个选择,我们选择相对引用那个(通过注释区别)。源码如下:

代码语言:javascript复制
/// A direct relative reference to an object that is not a function pointer.
template <typename T, bool Nullable, typename Offset>
class RelativeDirectPointer<T, Nullable, Offset,
    typename std::enable_if<!std::is_function<T>::value>::type>
    : private RelativeDirectPointerImpl<T, Nullable, Offset>
{
  using super = RelativeDirectPointerImpl<T, Nullable, Offset>;
public:
  using super::get;
  using super::super;
  
  RelativeDirectPointer &operator=(T *absolute) & {
    super::operator=(absolute);
    return *this;
  }

  operator typename super::PointerTy() const & {
    return this->get();
  }

  const typename super::ValueTy *operator->() const & {
    return this->get();
  }

  using super::isNull;
};

在源码中我们可以看到很多关于supper的东西,有两处都是通过get()方法返回的,分别是PointerTyValueTy,下面我们就来到父类方法中看看。父类是RelativeDirectPointerImpl,其部分源码如下:

代码语言:javascript复制
/// A relative reference to a function, intended to reference private metadata
/// functions for the current executable or dynamic library image from
/// position-independent constant data.
template<typename T, bool Nullable, typename Offset>
class RelativeDirectPointerImpl {
private:
  /// The relative offset of the function's entry point from *this.
  Offset RelativeOffset;
  
public:
  using ValueTy = T;
  using PointerTy = T*;
  
  PointerTy get() const & {
    // Check for null.
    if (Nullable && RelativeOffset == 0)
      return nullptr;
    
    // The value is addressed relative to `this`.
    uintptr_t absolute = detail::applyRelativeOffset(this, RelativeOffset);
    return reinterpret_cast<PointerTy>(absolute);
  }
}

这里同样有一个Offset类型RelativeOffset,以及get()方法,同样get()方法也会调用applyRelativeOffset函数进行地址的相加,关于applyRelativeOffset方法在介绍Parent的时候提到过。

这里面多了ValueTyPointerTyValueTy是传入泛型的值,PointerTy是其指针。

name中还有个const char,这里是直接用const char类型存储的类型的名称。

3.4.4 AccessFunctionPtr

AccessFunctionPtr的类型是TargetRelativeDirectPointer<Runtime, MetadataResponse(...), /*Nullable*/ true>

跟name一样的使用偏移指针TargetRelativeDirectPointer,只不过是const char替换成了MetadataResponse(...),点击MetadataResponse跳转后我们可以看到如下代码:

代码语言:javascript复制
MetadataResponse() : Metadata(nullptr) {}

/// A metadata response that might not be dynamically complete.
explicit MetadataResponse(llvm::Value *metadata, llvm::Value *state,
                        MetadataState staticLowerBoundState)
  : Metadata(metadata), DynamicState(state),
    StaticState(staticLowerBoundState) {
    assert(metadata && "must be valid");
}

所以这里只是一个Metadata的指针。

3.4.5 Fields

Fields的类型是TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor, /*nullable*/ true>

TargetRelativeDirectPointer就不多说了,这里看看FieldDescriptor点击跳转到其源码处,部分源码如下:

代码语言:javascript复制
// Field descriptors contain a collection of field records for a single
// class, struct or enum declaration.
class FieldDescriptor {
  const FieldRecord *getFieldRecordBuffer() const {
    return reinterpret_cast<const FieldRecord *>(this   1);
  }

public:
  const RelativeDirectPointer<const char> MangledTypeName;
  const RelativeDirectPointer<const char> Superclass;

  FieldDescriptor() = delete;

  const FieldDescriptorKind Kind;
  const uint16_t FieldRecordSize;
  const uint32_t NumFields;
  
}

这里有5个属性:

1. MangledTypeName

2. Superclass

3. kind

4. FieldRecordSize

5. NumFields

关于getFieldRecordBuffer函数的返回值FieldRecord源码如下:

代码语言:javascript复制
class FieldRecord {
  const FieldRecordFlags Flags;

public:
  const RelativeDirectPointer<const char> MangledTypeName;
  const RelativeDirectPointer<const char> FieldName;
.....  
}

FieldRecord主要是封装了一些属性,用于存储这些值。

3.4.6 NumFields

NumFields的类型是uint32_t这就没什么好说的了,一个整形存储属性个数

3.4.7 FieldOffsetVectorOffset

FieldOffsetVectorOffset也是个uint32_t的整形,存储偏移量。

3.5 Mirror取值

对于Description分析基本很透彻了,那么我们就回到最初的位置,看看Mirror都是怎样从Description取出相应的值的。

3.5.1 type

首先我们看看type是怎么取的:

首先是调用swift_reflectionMirror_normalizedType函数

代码语言:javascript复制
// 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; });
}

比如说这是个结构体,此时的impl就是个StructImpl类型,所以这里的typeStructImpl父类ReflectionMirrorImpl的属性type

3.5.2 count

关于count的获取首先是调用swift_reflectionMirror_count函数

代码语言:javascript复制
// func _getChildCount<T>(_: T, type: Any.Type) -> Int
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
intptr_t swift_reflectionMirror_count(OpaqueValue *value,
                                      const Metadata *type,
                                      const Metadata *T) {
  return call(value, T, type, [](ReflectionMirrorImpl *impl) {
    return impl->count();
  });
}

同样还以结构体为例,此时的implStructImpl,内部的count()函数:

代码语言:javascript复制
intptr_t count() {
    if (!isReflectable()) {
      return 0;
    }

    auto *Struct = static_cast<const StructMetadata *>(type);
    return Struct->getDescription()->NumFields;
}

这里的Struct就是个TargetStructMetadata类型,通过getDescription()函数获取到一个TargetStructDescriptor类型的Description,然后取NumFields的值就是我们要的count

3.5.3 属性名和属性值

我们知道在Mirror中通过其初始化方法返回一个提供该值子元素的AnyCollection<Child>类型的children集合,Child是一个元组(label: String?, value: Any),label是一个可选类型的属性名,value是属性值。

在分析internalReflecting函数的时候,我们说children是懒加载的,而加载的时候会调用getChild方法,getChild方法源码入下:

代码语言:javascript复制
internal func getChild<T>(of value: T, type: Any.Type, index: Int) -> (label: String?, value: Any) {
  var nameC: UnsafePointer<CChar>? = nil
  var freeFunc: NameFreeFunc? = nil
  
  let value = _getChild(of: value, type: type, index: index, outName: &nameC, outFreeFunc: &freeFunc)
  
  let name = nameC.flatMap({ String(validatingUTF8: $0) })
  freeFunc?(nameC)
  return (name, value)
}

getChild方法中还会调用_getChild方法,源码如下:

代码语言:javascript复制
@_silgen_name("swift_reflectionMirror_subscript")
internal func _getChild<T>(
  of: T,
  type: Any.Type,
  index: Int,
  outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
  outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
) -> Any

_getChild方法同样是使用@_silgen_name修饰符最终调用的C 中的swift_reflectionMirror_subscript函数。

代码语言:javascript复制
// We intentionally use a non-POD return type with this entry point to give
// it an indirect return ABI for compatibility with Swift.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
// func _getChild<T>(
//   of: T,
//   type: Any.Type,
//   index: Int,
//   outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
//   outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
// ) -> Any
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
AnyReturn swift_reflectionMirror_subscript(OpaqueValue *value, const Metadata *type,
                                           intptr_t index,
                                           const char **outName,
                                           void (**outFreeFunc)(const char *),
                                           const Metadata *T) {
  return call(value, T, type, [&](ReflectionMirrorImpl *impl) {
    return impl->subscript(index, outName, outFreeFunc);
  });
}
#pragma clang diagnostic pop

这里我们可以看到是调用了implsubscript函数,同样以结构体为例,我们在StructImpl中找到该函数,源码如下:

代码语言:javascript复制
AnyReturn subscript(intptr_t i, const char **outName,
                      void (**outFreeFunc)(const char *)) {
    auto fieldInfo = childMetadata(i, outName, outFreeFunc);

    auto *bytes = reinterpret_cast<char*>(value);
    auto fieldOffset = childOffset(i);
    auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes   fieldOffset);

    return copyFieldContents(fieldData, fieldInfo);
  }

通过subscript函数我们可以看到这里面还会调用childMetadata获取到fieldInfo,其实这里就是获取type,也就是属性名,通过childOffset函数和index获取到对于的偏移量,最后根据内存偏移去到属性值。

childMetadata源码:

代码语言:javascript复制
const FieldType childMetadata(intptr_t i, const char **outName,
                                void (**outFreeFunc)(const char *)) {
    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;
  }

这里面的关键点是调用调用getFieldAt函数获取属性名称,

getFieldAt源码:

代码语言:javascript复制
static std::pair<StringRef /*name*/, FieldType /*fieldInfo*/>
getFieldAt(const Metadata *base, unsigned index) {
  using namespace reflection;
  
  // If we failed to find the field descriptor metadata for the type, fall
  // back to returning an empty tuple as a standin.
  auto failedToFindMetadata = [&]() -> std::pair<StringRef, FieldType> {
    auto typeName = swift_getTypeName(base, /*qualified*/ true);
    missing_reflection_metadata_warning(
      "warning: the Swift runtime found no field metadata for "
      "type '%*s' that claims to be reflectable. Its fields will show up as "
      "'unknown' in Mirrorsn",
      (int)typeName.length, typeName.data);
    return {"unknown", FieldType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))};
  };

  auto *baseDesc = base->getTypeContextDescriptor();
  if (!baseDesc)
    return failedToFindMetadata();

  auto *fields = baseDesc->Fields.get();
  if (!fields)
    return failedToFindMetadata();
  
  auto &field = fields->getFields()[index];
  // Bounds are always valid as the offset is constant.
  auto name = field.getFieldName();

  // Enum cases don't always have types.
  if (!field.hasMangledTypeName())
    return {name, FieldType::untypedEnumCase(field.isIndirectCase())};

  auto typeName = field.getMangledTypeName();

  SubstGenericParametersFromMetadata substitutions(base);
  auto typeInfo = swift_getTypeByMangledName(MetadataState::Complete,
   typeName,
   substitutions.getGenericArgs(),
   [&substitutions](unsigned depth, unsigned index) {
     return substitutions.getMetadata(depth, index);
   },
   [&substitutions](const Metadata *type, unsigned index) {
     return substitutions.getWitnessTable(type, index);
   });

  // If demangling the type failed, pretend it's an empty type instead with
  // a log message.
  if (!typeInfo.getMetadata()) {
    typeInfo = TypeInfo({&METADATA_SYM(EMPTY_TUPLE_MANGLING),
                         MetadataState::Complete}, {});
    missing_reflection_metadata_warning(
      "warning: the Swift runtime was unable to demangle the type "
      "of field '%*s'. the mangled type name is '%*s'. this field will "
      "show up as an empty tuple in Mirrorsn",
      (int)name.size(), name.data(),
      (int)typeName.size(), typeName.data());
  }

  auto fieldType = FieldType(typeInfo.getMetadata());
  fieldType.setIndirect(field.isIndirectCase());
  fieldType.setReferenceOwnership(typeInfo.getReferenceOwnership());
  return {name, fieldType};
}

我们可以看到在上面这个方法中:

  • 首先通过getTypeContextDescriptor获取baseDesc,也就是我们说的Description
  • 然后通过Fields.get()获取到fields
  • 接着通过getFields()[index]或取对应的field
  • 最后通过getFieldName()函数获取到属性名称
  • getTypeContextDescriptor函数在struct TargetMetadata中,
  • 通过这个函数获取到一个TargetStructDescriptor,它的父类的父类TargetTypeContextDescriptor中的Fields属性
  • Fields属性的类型TargetRelativeDirectPointer中有get方法
  • 实际中使用的FieldDescriptor类中getFieldRecordBuffer方法返回的FieldRecord中的getFieldName函数

getFields 源码:

代码语言:javascript复制
  const_iterator begin() const {
    auto Begin = getFieldRecordBuffer();
    auto End = Begin   NumFields;
    return const_iterator { Begin, End };
  }

  const_iterator end() const {
    auto Begin = getFieldRecordBuffer();
    auto End = Begin   NumFields;
    return const_iterator { End, End };
  }

  llvm::ArrayRef<FieldRecord> getFields() const {
    return {getFieldRecordBuffer(), NumFields};
  }

关于getFields我们可以看到这是一块连续的空间,在beginend中:

  • begin就是getFieldRecordBuffer
  • getFieldRecordBuffer就是Begin NumFields
  • 所以这就是一块连续内存的访问

childOffset 源码:

分析完了属性名的获取,我们来看看偏移量的获取

代码语言:javascript复制
intptr_t childOffset(intptr_t i) {
    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];
  }

这里面是调用TargetStructMetadata中的getFieldOffsets函数源码如下:

代码语言:javascript复制
/// Get a pointer to the field offset vector, if present, or null.
  const uint32_t *getFieldOffsets() const {
    auto offset = getDescription()->FieldOffsetVectorOffset;
    if (offset == 0)
      return nullptr;
    auto asWords = reinterpret_cast<const void * const*>(this);
    return reinterpret_cast<const uint32_t *>(asWords   offset);
  }

我们可以看到这里统一是通过获取Description中的属性,这里使用的属性是FieldOffsetVectorOffset

获取到偏移值后通过内存偏移即可获取到属性值。

3.6 小结

至此我们对Mirror的原理基本探索完毕了,现在总结一下:

  1. Mirror通过初始化方法返回一会Mirror实例
  2. 这个实例对象根据传入对象的类型去对应的Metadata中找到Description
  3. Description可以获取name也就是属性的名称
  4. 通过内存偏移获取到属性值
  5. 还可以通过numFields获取属性的个数

下面通过该流程图总结一下swift中的mirror对结构体进行反射的主要流程

关于其他类型的反射也大同小异,还有元组、枚举、类、元数据以及不透明类型的反射,当然也有不完全支持反射的类型,比如结构体就是不完全支持反射的类型,感兴趣的可以继续探索一下。

  • swift中的type(of:)dump(t)就是基于Mirror的反射原理来实现的
  • Swift中的json解析框架HandyJSON的主要原理与Mirror类似,本质上就是利用metadata中的Description,通过字段的访问,做内存的赋值。

4. 仿写 Mirror

为了加深对Mirror的理解,我们使用Swift语言仿写一下。还是以结构体为例。

4.1 TargetStructMetadata

首先我们需要拥有一个结构体的元数据结构,这里我们命名为StructMetadata,里面有继承的kindDescriptor属性,这里的Descriptor属性是一个TargetStructDescriptor类型的指针。仿写代码如下:

代码语言:javascript复制
struct StructMetadata{
    var kind: Int
    var Descriptor: UnsafeMutablePointer<StructDescriptor>
}
4.2 TargetStructDescriptor

4.1中我们用到的Descriptor属性的内部结构现在也来实现一下。这个是Mirror中用到很多的属性。对于结构体来说其内部有7个属性

  1. flag是个32位的整形,我们用Int32代替
  2. parent是记录父类的,类型是TargetRelativeContextPointer<Runtime>,这里也可以用Int32代替
  3. name记录类型的,它的类型是TargetRelativeDirectPointer<char>,所以我们需要实现一个TargetRelativeDirectPointer
  4. AccessFunctionPtrname类似,内部是个指针
  5. Fields也与name类似,内部是个FieldDescriptor
  6. NumFields使用Int32
  7. FieldOffsetVectorOffset也是用Int32

仿写实现如下:

代码语言:javascript复制
struct StructDescriptor {
    let flags: Int32
    let parent: Int32
    var name: RelativePointer<CChar>
    var AccessFunctionPtr: RelativePointer<UnsafeRawPointer>
    var Fields: RelativePointer<FieldDescriptor>
    var NumFields: Int32
    var FieldOffsetVectorOffset: Int32
}
4.3 TargetRelativeDirectPointer
  • TargetRelativeDirectPointerRelativeDirectPointer的别名,其内部有一个继承的RelativeOffset属性,是int32_t类型,我们可以用Int32代替。
  • 还有一个get方法。内部通过指针偏移获取值

仿写实现如下:

代码语言:javascript复制
struct RelativePointer<T> {
    var offset: Int32

    mutating func get() -> UnsafeMutablePointer<T>{
        let offset = self.offset

        return withUnsafePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
    }
}

4.4 FieldDescriptor

FieldDescriptorMirror反射中有着很重要的作用,其内部有5个属性:

  1. MangledTypeNameRelativeDirectPointer<const char>类型,我们使用RelativePointer<CChar>代替
  2. SuperclassMangledTypeName一样
  3. kindFieldDescriptorKind类型,实际是uint16_t,这里我们使用UInt16代替
  4. fieldRecordSizeuint16_t也使用使用UInt16代替
  5. numFields使用Int32代替
  6. fields,其实从属性上看不到有这个,但是这里面有个getFieldRecordBuffer方法,通过this 1的方式一个一个的访问属性,所以这是一块连续的内存空间,我们使用fields代替

仿写代码如下:

代码语言:javascript复制
struct FieldDescriptor {
    var MangledTypeName: RelativePointer<CChar>
    var Superclass: RelativePointer<CChar>
    var kind: UInt16
    var fieldRecordSize: Int16
    var numFields: Int32
    var fields: FieldRecord //连续的存储空间
}
4.5 FieldRecord

FieldRecord存储着属性的相关信息,其内部有三个属性

  1. FlagsFieldRecordFlags类型实际是uint32_t,这里我们使用Int32代替
  2. MangledTypeName使用RelativePointer<CChar>代替
  3. FieldName使用RelativePointer<CChar>代替

仿写带如下:

代码语言:javascript复制
struct FieldRecord {
    var Flags: Int32
    var MangledTypeName: RelativePointer<CChar>
    var FieldName: RelativePointer<CChar>
}
▐ 4.6 测试

下面我们使用内存绑定的计数访问一个结构体

定义一个结构体:

代码语言:javascript复制
struct Person {
    var name: String = "xiaohei"
    var age: Int = 18
    var height = 1.85
}

var p = Person()

4.6.1 绑定结构体内存

使用unsafeBitCast按位强转,将Person绑定到StructMetadata上,这个操作非常危险,没有任何校验和修饰

代码语言:javascript复制
let ptr = unsafeBitCast(Person.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)

4.6.2 打印类型和属性个数

下面我们就打印一下结构体的类型(也就是它的名称)和其中属性的个数:

代码语言:javascript复制
let namePtr = ptr.pointee.Descriptor.pointee.name.get()

print(String(cString: namePtr))
print(ptr.pointee.Descriptor.pointee.NumFields)

打印结果:

这里我们就可以看到结构体的名称和其属性个数的正确打印了。

4.6.3 打印属性名称

下面我们就来打印一下属性的名称,首先是获取到FieldDescriptor的指针,然后通过内存偏移的方式访问每一个FieldRecord,最后在访问FieldRecord中的属性名。代码如下:

代码语言:javascript复制
let fieldDescriptorPtr = ptr.pointee.Descriptor.pointee.Fields.get()

let recordPtr = withUnsafePointer(to: &fieldDescriptorPtr.pointee.fields) {
    return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: 2))
}

print(String(cString: recordPtr.pointee.FieldName.get()))

打印结果:

此时我们就可以看到第三属性height的打印,如果advanced(by: 0)则打印第一个属性,以此类推。

4.6.1 打印属性值

下面我们访问一下属性值:

首先是获取属性偏移量的数组,也就是getFieldOffsets函数返回的值。根据源码:

  • 首先获取FieldOffsetVectorOffset的值
  • 然后在加上this也就是当前Metadata的指针
  • 这里我们将仿写的StructMetadata的指针ptr重绑定为Int
  • 源码中加上FieldOffsetVectorOffset,这里我们就移动FieldOffsetVectorOffset
  • 然后将上述移动后的绑定为一个Int32的指针
  • 最后使用UnsafeBufferPointer和属性个数创建一个buffer数组指针
  • 接下来我们就可以从数组中取出每个属性的偏移值
  • 然后取出结构体实例p的内存地址
  • 然后按照buffer数组中的偏移值进行偏移,重绑定为属性的类型
  • 最后就可以打印出属性值了

实现代码:

代码语言:javascript复制
var bufferPtr = UnsafeBufferPointer(start: UnsafeRawPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self).advanced(by: numericCast(ptr.pointee.Descriptor.pointee.FieldOffsetVectorOffset))).assumingMemoryBound(to: Int32.self), count: Int(ptr.pointee.Descriptor.pointee.NumFields))

var fieldOffset = bufferPtr[2]

var valuePtr = withUnsafeMutablePointer(to: &p) { $0 }

var bufferPtr1 = UnsafeRawPointer(UnsafeRawPointer(valuePtr).advanced(by: numericCast(bufferPtr[0]))).assumingMemoryBound(to: String.self)
print(bufferPtr1.pointee)

var bufferPtr2 = UnsafeRawPointer(UnsafeRawPointer(valuePtr).advanced(by: numericCast(bufferPtr[1]))).assumingMemoryBound(to: Int.self)
print(bufferPtr2.pointee)

var bufferPtr3 = UnsafeRawPointer(UnsafeRawPointer(valuePtr).advanced(by: numericCast(bufferPtr[2]))).assumingMemoryBound(to: Double.self)
print(bufferPtr3.pointee)

打印结果:

0 人点赞