恩恩真靓咯 發表於 2022-6-17 12:39:57

Swift进阶教程Mirror反射示例详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>元类型与.self</li><ul class="second_class_ul"><li>AnyObject</li><li>AnyClass</li><li>Any</li><li>type(Of:)</li></ul><li>self</li><ul class="second_class_ul"><li>self在方法里面的作用</li><li>Self引用</li></ul><li>Swift Runtime</li><ul class="second_class_ul"></ul><li>Mirror</li><ul class="second_class_ul"><li>Mirror的基本用法</li><li>Mirror的简单应用-JSON解析</li><li>Mirror源码解析</li></ul><li>Enum Metadata探索</li><ul class="second_class_ul"><li>还原TargetEnumMetadata</li><li>还原TargetEnumDescriptor</li><li>相对偏移指针</li><li>打印枚举中的属性</li></ul><li>Struct Metadata探索</li><ul class="second_class_ul"><li>获取结构体的属性</li><ul class="third_class_ul"><li>swift_getTypeByMangledNameInContext 函数</li><li>获取属性的值</li></ul></ul></ul></div><p class="maodian"></p><h2>元类型与.self</h2>
<p class="maodian"></p><h3>AnyObject</h3>
<p>在Swift开发中,我们经常会使用AnyObject来代表任意类的实例、类的类型、以及仅类遵守的协议。</p>
<p>代表任意类的实例、类的类型</p>
<div class="jb51code"><pre class="brush:cpp;">class LGTeacher {
    var age = 18
}
var t = LGTeacher()
var t1: AnyObject = t //代表LGTeacher类的实例
var t2: AnyObject = LGTeacher.self //代表LGTeacher类的类型
</pre></div>
<p>代表仅类遵循的协议</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464601.jpg" /></p>
<p>这里使用了AnyObjcct来修饰协议,可以看到,当struct类型的structLGTeacher去遵循协议时,编译器会报错。只允许class类型遵循协议。</p>
<p>我们在和OC交互的过程中,也经常通过AnyObject来表示某种类型的instance。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464602.jpg" /></p>
<p>我们在代码编写的过程中有时候不知道具体的类型,⽤AnyObject来表示;那如果我们知道了确定了类型,该如何把AnyObject转换成具体的类型,这⾥我们使⽤三个关键字as,as?,as!进行类型转换。</p>
<p class="maodian"></p><h3>AnyClass</h3>
<p>AnyClass代表了任意实例的类型,我们可以从源码里面去查看AnyClass的定义</p>
<div class="jb51code"><pre class="brush:cpp;">public typealias AnyClass = AnyObject.Type
</pre></div>
<p>我们可以看到,AnyClass的定义就是AnyObject.Type,也就是实例对象的类型,所以我们不能用具体的实例对象赋值给AnyClass,编译器会报错。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464603.jpg" /></p>
<p class="maodian"></p><h3>Any</h3>
<p>Any可以代表任意类型(枚举、结构体、类),也包括函数类型和Optional类型。</p>
<div class="jb51code"><pre class="brush:cpp;">var array: =
</pre></div>
<p>上面这段代码会报错,因为AnyObject代表任意类的实例和类型,而我们传入的是Int类型,是属于值类型,无法用AnyObject表示。这时,我们要使用Any。</p>
<div class="jb51code"><pre class="brush:cpp;">var array: =
</pre></div>
<p class="maodian"></p><h3>type(Of:)</h3>
<p>type(Of:)⽤来获取⼀个值的动态类型。什么是动态类型呢?</p>
<ul><li>静态类型(static type),这个是在编译时期确定的类型。</li><li>动态类型(dynamic Type),这个是在运⾏时期确定的类型。</li></ul>
<p>接下来我们用代码来描述静态类型和动态类型。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464604.jpg" /></p>
<p>可以看到,在编译期间就能知道a的类型是Int,因为初始化数据的时候赋值的是Int类型数据,但是在test方法的形参定义的类型是Any,因此在编译期间并不知道其形参类型,所以将a传入时也被编译器当作是Any类型,但是在运行时,可以动态得知传入的a是Int类型,因此type(of:)就可以用来获取当前值在运行时的实际类型。</p>
<p class="maodian"></p><h2>self</h2>
<p>在Swift中,我们可以使用类型或者实例对象来访问self。</p>
<p>T.self:&nbsp;T&nbsp;是实例对象,当前&nbsp;T.self&nbsp;返回的就是实例对象本身。</p>
<p>如果&nbsp;T&nbsp;是类,当前&nbsp;T.self&nbsp;返回的就是元类型。</p>
<p>我们通过代码验证一下。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464605.jpg" /></p>
<p>从代码中,我们可以看到,当我po t 和po t1时,可以看到t和t1指向同一个地址。然后当我po t2时,发现打印出来的是LGTeaher类型。接下来我用x/8g命令打印t2,打印出t2的内存地址。再打印出t的内存地址,然后打印t里面存储metadata的内存地址。发现和t2的内存地址一模一样。</p>
<p>所以,在&nbsp;<strong>T.self</strong>&nbsp;中,当&nbsp;<strong>T</strong>&nbsp;为实例对象的时候,<strong>T.self</strong>&nbsp;返回的是实例对象本身。当&nbsp;<strong>T</strong>&nbsp;为类的时候,<strong>T.self</strong>&nbsp;返回的是一个元类型,也就是前面讲的元数据(metadata)。</p>
<p class="maodian"></p><h3>self在方法里面的作用</h3>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464606.jpg" /></p>
<p>我们可以看到,在实例方法中,self指向的就是当前调用方法的实例对象,而在类方法里面,self指向的就是当前类型的元数据。</p>
<p class="maodian"></p><h3>Self引用</h3>
<p>Self类型不是特定类型,⽽是让您⽅便地引⽤当前类型,⽽⽆需重复或知道该类型的名称。</p>
<p>在协议声明或协议成员声明中,Self类型是指最终符合协议的类型。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464607.jpg" /></p>
<p>Self作为实例方法的返回类型代表自身类型</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464608.jpg" /></p>
<p>计算属性/实例方法中访问自身的类型属性,类型方法</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022061711464609.jpg" /></p>
<p class="maodian"></p><h2>Swift Runtime</h2>
<p>在OC中,我们可以通过Runtime特性来获取一个类的属性和方法。而Swift中没有Runtime,能使用OC的Runtime获取一个类的属性和方法吗?我们通过代码测试一下。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646010.jpg" /></p>
<p>代码结果没有打印任何东西。</p>
<p>现在我们往LGTeacher类的属性和方法前面加上@objc标识符,看看会有什么结果?</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646011.jpg" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646012.jpg" /></p>
<p>这时可以通过Runtime API打印方法和属性名,但是OC无法进行调动。</p>
<p>对于继承NSObject类的Swift类,如果我们想要动态的获取当前的属性和⽅法,必须在其声明前添加@objc 关键字,否则也是没有办法通过Runtime API获取的。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646013.jpg" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646014.jpg" /></p>
<p>继承NSObject类的Swift类,没有在属性和方法声明前面添加@objc 关键字,只能获取到init方法。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646015.jpg" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646016.jpg" /></p>
<p>继承NSObject类的Swift类,在属性和方法声明前面添加@objc关键字,不仅可以使用Runtime API获取属性和方法名,也可以在OC中被调用。</p>
<p>还有一些和Swift Runtime相关的结论,我在这里总结出来,可以自己去试验一下。</p>
<ul><li>纯swift类没有动态性,但在⽅法、属性前添加dynamic修饰,可获得动态性。</li><li>继承⾃NSObject的swift类,其继承⾃⽗类的⽅法具有动态性,其它⾃定义⽅法、属性想要获得动态性,需要添加dynamic修饰。&nbsp;</li><li>若⽅法的参数、属性类型为swift特有、⽆法映射到objective-c的类型(如Character、Tuple),则&nbsp;此⽅法、属性⽆法添加dynamic修饰(编译器报错)</li></ul>
<p class="maodian"></p><h2>Mirror</h2>
<p class="maodian"></p><h3>Mirror的基本用法</h3>
<p>所谓反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。在使⽤OC开发时很少强调其反射概念,因为OC的Runtime要⽐其他语⾔中的反射强⼤的多。但是 Swift 是⼀⻔类型安全的语⾔,不⽀持我们像&nbsp;OC&nbsp;那样直接操作,它的标准库仍然提供了反射机制来让我们访问成员信息,Swift&nbsp;的反射机制是基于⼀个叫&nbsp;Mirror&nbsp;的结构体来实现的。然后就可以通过它查询这个实例。</p>
<p>Mirror的基本使用如下</p>
<div class="jb51code"><pre class="brush:cpp;">class LGTeacher {
    var age:Int = 18
    func teach() {
      print("teach")
    }
}
//⾸先通过构造⽅法构建⼀个Mirror实例,这⾥传⼊的参数是 Any,也就意味着当前可以是类,结构体,枚举等
let mirror = Mirror(reflecting: LGTeacher())
//接下来遍历 children 属性,这是⼀个集合
for pro in mirror.children {
    //然后我们可以直接通过 label 输出当前的名称,value 输出当前反射的值
    print("\(pro.label) : \(pro.value)")
}
</pre></div>
<p class="maodian"></p><h3>Mirror的简单应用-JSON解析</h3>
<div class="jb51code"><pre class="brush:cpp;">class LGTeacher {
    var age:Int = 18
    var name = "FY"
}
enum JSONMapError: Error {
    case emptyKey
    case notConformProtocol
}
protocol JSONMap {
    func jsonMap() -&gt; Any
}
extension JSONMap {
    func jsonMap() -&gt; Any {
      let mirror = Mirror(reflecting: self)
      guard !mirror.children.isEmpty else {
            return self
      }
   
      var result: = [:]
      
      for child in mirror.children {
            if let value = child.value as? JSONMap {
                if let key = child.label {
                  result = try? value.jsonMap()
                }
            } else {
                return JSONMapError.notConformProtocol
            }
      }      
      return result
    }
}
extension LGTeacher: JSONMap{}
extension Int: JSONMap{}
extension String: JSONMap{}
print(LGTeacher().jsonMap())
//
["age": 18, "name": "FY"]
</pre></div>
<p class="maodian"></p><h3>Mirror源码解析</h3>
<p>⾸先我们现在源⽂件⾥⾯搜索Mirror.Swift,在源码中我们可以很清晰的看到Mirror是由结构体实现的,我们忽略掉⼀些细节,快速定位到初始化的⽅法</p>
<div class="jb51code"><pre class="brush:cpp;">public init(reflecting subject: Any) {
    if case let customized as CustomReflectable = subject {
      self = customized.customMirror
    } else {
      self = Mirror(internalReflecting: subject)
    }
}
</pre></div>
<p>可以看到,这⾥接受⼀个Any类型的参数,同样的这⾥有⼀个if case的写法来判断当前的subject是否遵循了customReflectable协议,如果是我们就直接调⽤customMirror, 否则就进⾏下级函数的调⽤。</p>
<p>这⾥有两个需要注意的点if case的写法,这⾥其实枚举Case的模式匹配,和我们的Switch⼀样,这⾥是只有⼀个case的&nbsp;switch&nbsp;语句。</p>
<p>于此同时这⾥出现了⼀个customRefletable的协议。我们来看一下它的用法。⾸先我们遵循&nbsp;customReflectable&nbsp;协议,并实现其中的属性customMirror,customMirror会返回⼀个Mirror对象。代码如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646017.jpg" /></p>
<p>这里通过遵循customReflectable&nbsp;协议并实现了其中的计算属性customMirror,主要作用是当我们使用lldb debug的时候,可以提供详细的属性信息。</p>
<p>我们接下来看如果不遵循customRefletable协议的类,那么就会走Mirror(internalReflecting: subject)代码。</p>
<p>全局搜索internalReflecting,在ReflectionMirror.swift文件里面找到了这个方法的具体实现。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646018.jpg" /></p>
<p>从代码里面我们可以看到,首先需要获取subject的真实类型信息。然后再获取subject的属性信息。 而获取subject的真实类型信息则是通过_getNormalizedType这个方法来获取的。搜索这个方法,然后我们就可以找到它的代码。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646019.jpg" /></p>
<p>这里使用了一个编译器字段&nbsp;@ silgen_name&nbsp;其实是&nbsp;Swift&nbsp;的一个隐藏符号,作用是将某个&nbsp;C/C++语言函数直接映射为&nbsp;Swift&nbsp;函数。也可以理解为为&nbsp;C++&nbsp;代码的&nbsp;swift_reflectionMirror_normalizedType&nbsp;函数定义一个在&nbsp;swift&nbsp;中使用的别名&nbsp;_getNormalizedType。</p>
<p>所以调用了_getNormalizedType方法实际上是调用了swift_reflectionMirror_normalizedType方法,我在ReflectionMirror.cpp文件中找到了具体实现。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646020.jpg" /></p>
<p>从代码里面可以知道,通过call函数调用了ReflectionMirrorImpl类,然后返回这个类的类型。</p>
<p>我们先看一下ReflectionMirrorImpl类的具体内容。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646021.jpg" /></p>
<p>从注释中,我们可以知道,这是一个抽象基类,也就是说不同的类型反射需要不同的类实现。</p>
<p>我们接下来看一下call函数的具体实现</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646022.jpg" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646023.jpg" /></p>
<p>在call函数中有一个Switch方法。根据不同的类型,调用不同的ReflectionMirrorImpl类。 我们就取EnumIpml类去探个究竟。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646024.jpg" /></p>
<p>上面代码就是EnumIpml类的具体实现。首先是isReflectable()这个方法。这个方法返回这个类型是否可以被反射,也就是找到metadata,再找到metadata中存储的Description,通过它里面存储的&nbsp;isReflectable来确定。</p>
<p>接下来我们看一下getInfo方法。在代码里面我们可以看到,获取name、info属性信息主要是通过getFieldAt来获取。我们现在就去查看getFieldAt方法。具体代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">static std::pair&amp;lt;StringRef /*name*/, FieldType /*fieldInfo*/&amp;gt;
getFieldAt(const Metadata *base, unsigned index) {
    using namespace reflection;
    auto failedToFindMetadata = [&amp;amp;]() -&amp;gt; std::pair&amp;lt;StringRef, FieldType&amp;gt; {
      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 Mirrors\n",
            (int)typeName.length, typeName.data);
      return {"unknown", FieldType(&amp;amp;METADATA_SYM(EMPTY_TUPLE_MANGLING))};
    };
    auto *baseDesc = base-&amp;gt;getTypeContextDescriptor();
    if (!baseDesc) return failedToFindMetadata();
    auto *fields = baseDesc-&amp;gt;Fields.get();
    if (!fields) return failedToFindMetadata();
    auto &amp;amp;field = fields-&amp;gt;getFields();
// 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 result = swift_getTypeByMangledName(
      MetadataState::Complete, typeName, substitutions.getGenericArgs(),
      [&amp;amp;substitutions](unsigned depth, unsigned index) {
            return substitutions.getMetadata(depth, index);
      },
      [&amp;amp;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.
    TypeInfo typeInfo;
    if (result.isError()) {
      typeInfo = TypeInfo({&amp;amp;METADATA_SYM(EMPTY_TUPLE_MANGLING),
      MetadataState::Complete}, {});
      auto *error = result.getError();
      char *str = error-&amp;gt;copyErrorString();
      missing_reflection_metadata_warning(
            "warning: the Swift runtime was unable to demangle the type "
            "of field '%*s'. the mangled type name is '%*s': %s. this field will "
            "show up as an empty tuple in Mirrors\n",
            (int)name.size(), name.data(), (int)typeName.size(), typeName.data(),
            str);
      error-&amp;gt;freeErrorString(str);
    } else {
      typeInfo = result.getType();
    }
    auto fieldType = FieldType(typeInfo.getMetadata());
    fieldType.setIndirect(field.isIndirectCase());
    fieldType.setReferenceOwnership(typeInfo.getReferenceOwnership());
    fieldType.setIsVar(field.isVar());
    return {name, fieldType};
</pre></div>
<p>这里可以看到可以看到 所有的信息都是通过Metadata、getDescription()、FieldDescrition&nbsp;这几个东西来去实现的,⼀个是当前类型的元数据,⼀个是当前类型的描述,⼀个是对当前类型属性的描述。</p>
<p class="maodian"></p><h2>Enum Metadata探索</h2>
<p>我们在类和结构体这篇文章里面,描述了类的Metadata结构,并把它的C++代码转换成了Swift代码。我们这次来尝试转换Enum和struct的MetaData结构。首先来探索Enum的Metadata结构。</p>
<p class="maodian"></p><h3>还原TargetEnumMetadata</h3>
<p>通过源码全局搜索EnumMetadata,我们找到了TargetEnumMetadata。沿着TargetEnumMetadata的继承链往上查找,TargetEnumMetadata-&gt; TargetValueMetadata -&gt; TargetMetadata</p>
<div class="jb51code"><pre class="brush:cpp;">struct TargetEnumMetadata : public TargetValueMetadata&lt;Runtime&gt; {
}
struct TargetValueMetadata : public TargetMetadata&lt;Runtime&gt; {
    TargetSignedPointer&lt;Runtime, const TargetValueTypeDescriptor&lt;Runtime&gt; *Description;
}
struct TargetMetadata {
    StoredPointer Kind;
}
</pre></div>
<p>从上面的源码中我们可以知道,TargetMetadata有一个属性Kind,这个Kind主要是存储MetadataKind类,是个int_32类型。TargetValueMetadata里面有Description属性,因此我们可以把TargetEnumMetadata转成这样的结构体</p>
<div class="jb51code"><pre class="brush:cpp;">struct TargetEnumMetadata {
    var kind: Int
    var typeDescriptor: UnsafeRawPointer
}
</pre></div>
<p class="maodian"></p><h3>还原TargetEnumDescriptor</h3>
<p>接下来,我们要还原typeDescriptor的结构,虽然在TargetValueMetadata类中是TargetValueTypeDescriptor类,而我们在TargetMetadata发现Description属性是TargetEnumDescriptor类,所以,Description属性和TargetMetadata一样,应该也是有继承链的。</p>
<p>在TargetMetadata源码中,获取Description属性的方法是这样的:</p>
<div class="jb51code"><pre class="brush:cpp;">const TargetEnumDescriptor&lt;Runtime&gt; *getDescription() const {
    return llvm::cast&lt;TargetEnumDescriptor&lt;Runtime&gt;&gt;(this-&gt;Description);
}
</pre></div>
<p>然后我们去源码里面查看TargetEnumDescriptor类,得到它的继承链TargetEnumDescriptor-&gt; TargetValueTypeDescriptor -&gt; TargetTypeContextDescriptor-&gt; TargetContextDescriptor 它们包含的属性的代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">class TargetEnumDescriptor final : public TargetValueTypeDescriptor&lt;Runtime&gt;,
      public TrailingGenericContextObjects&lt;TargetEnumDescriptor&lt;Runtime&gt;,
      TargetTypeGenericContextDescriptorHeader,
/*additional trailing objects*/
      TargetForeignMetadataInitialization&lt;Runtime&gt;,
      TargetSingletonMetadataInitialization&lt;Runtime&gt;,
      TargetCanonicalSpecializedMetadatasListCount&lt;Runtime&gt;,
      TargetCanonicalSpecializedMetadatasListEntry&lt;Runtime&gt;,
      TargetCanonicalSpecializedMetadatasCachingOnceToken&lt;Runtime&gt;&gt; {
    uint32_t NumPayloadCasesAndPayloadSizeOffset;
    uint32_t NumEmptyCases;
}
class TargetValueTypeDescriptor: public TargetTypeContextDescriptor&lt;Runtime&gt; {
}
class TargetTypeContextDescriptor: public TargetContextDescriptor&lt;Runtime&gt; {
    TargetRelativeDirectPointer&lt;Runtime, const char, /*nullable*/ false&gt; Name;
    TargetRelativeDirectPointer&lt;Runtime, MetadataResponse(...), /*Nullable*/ true&gt; AccessFunctionPtr;
    TargetRelativeDirectPointer&lt;Runtime, const reflection::FieldDescriptor,/*nullable*/ true&gt; Fields;
}
struct TargetContextDescriptor {
    ContextDescriptorFlags Flags;
    TargetRelativeContextPointer&lt;Runtime&gt; Parent;
}
</pre></div>
<p>从上面的代码我们可以把TargetEnumDescriptor使用Swift把它还原出来,还原的代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">struct TargetEnumDescriptor{
    var flags: Int32
    var parent: TargetRelativeDirectPointer&lt;UnsafeRawPointer&gt;
    var name: TargetRelativeDirectPointer&lt;CChar&gt;
    var accessFunctionPointer: TargetRelativeDirectPointer&lt;UnsafeRawPointer&gt;
    var fieldDescriptor: TargetRelativeDirectPointer&lt;UnsafeRawPointer&gt;
    var NumPayloadCasesAndPayloadSizeOffset: UInt32
    var NumEmptyCases: UInt32
}
</pre></div>
<p>此时TargetMetadata中的数据结构也可以修改一下</p>
<div class="jb51code"><pre class="brush:cpp;">struct TargetEnumMetadata{
    var kind: Int
    var typeDescriptor: UnsafeMutablePointer&lt;TargetEnumDescriptor&gt;
}
</pre></div>
<p class="maodian"></p><h3>相对偏移指针</h3>
<p>在上面的源码中,我们可以发现,TargetValueTypeDescriptor中的属性、比如name、Fields等,他们的类型都是用TargetRelativeDirectPointer来定义。我们现在来看一下TargetRelativeDirectPointer是什么?</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646025.jpg" /></p>
<p>从上面的源码定义可以知道,它是一个模板类,(接收三个参数,⼀个是Runtime, ⼀个是Pointee&nbsp;,&nbsp;Bool类型默认为True)。接下来我们看一下RelativeDirectPointer。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646026.jpg" /></p>
<p>这个指针类代码比较简单,其中&nbsp;T&nbsp;就是我们进来的类型,Offset就是int32_t&nbsp;的类型,从字面意思上看应该是偏移量之类的。我们再看下它的get()方法。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646027.jpg" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220617114646028.jpg" /></p>
<p>从以上代码,我们可以看出TargetRelativeDirectPointer应该是一个用来相对寻址的指针类。在&nbsp;Swift中引⽤⼀个实例对象有两种情况:一种是直接寻址,另外一种是相对寻址。比如&nbsp;<strong>TargetEnumDescriptor</strong>&nbsp;中的&nbsp;Name,这个&nbsp;Name&nbsp;存储的值并不是&nbsp;Name&nbsp;表意上的值,Name存储的是一个叫做<strong>相对偏移量或者叫偏移信息</strong>。此时,我们拿到&nbsp;Name&nbsp;的值的内存地址做法是:<strong>Name 的内存地址 + 相对偏移量</strong>。在 Swift 里面有很多这样的偏移信息,这样做可以节省内存空间,避免存储大量的内存地址。</p>
<p>对此,我们把TargetRelativeDirectPointer给还原出来。还原代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">struct TargetRelativeDirectPointer&lt;Pointee&gt;{
    var offset: Int32
   
    mutating func getmeasureRelativeOffset() -&gt; UnsafeMutablePointer&lt;Pointee&gt;{
      let offset = **self**.offset   
      return withUnsafePointer(to: &amp;self) { p in
         return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
      }
    }
}
</pre></div>
<p class="maodian"></p><h3>打印枚举中的属性</h3>
<p>最后,我们来打印一下枚举中的属性。代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let ptr = unsafeBitCast(Planet.self as Any.Type to:UnsafeMutablePointer&lt;TargetEnumMetadata&gt;.self)
let namePtr = ptr.pointee.typeDescriptor.pointee.name.getmeasureRelativeOffset()
print("name: ",String(cString: namePtr))
print("NumPayloadCasesAndPayloadSizeOffset ",ptr.pointee.typeDescriptor.pointee.NumPayloadCasesAndPayloadSizeOffset)
print("NumEmptyCases ",ptr.pointee.typeDescriptor.pointee.NumEmptyCases)
</pre></div>
<p>打印结果如下</p>
<div class="jb51code"><pre class="brush:cpp;">name:Planet
NumPayloadCasesAndPayloadSizeOffset0
NumEmptyCases8
</pre></div>
<p class="maodian"></p><h2>Struct Metadata探索</h2>
<p>现在我们来解析一下struct类型的Metadata。</p>
<p>首先,和上面的Enum一样,通过全局搜索,找到struct的MetaData是TargetStructMetadata类。通过它的继承链TargetStructMetadata-&gt; TargetValueMetadata -&gt; TargetMetadata可以知道,TargetStructMetadata的数据结构和TargetEnumMetadata一样,因此,我们可以还原一下 TargetStructMetadata的数据结构如下:</p>
<div class="jb51code"><pre class="brush:cpp;">struct TargetStructMetadata {
    var kind: Int
    var typeDescriptor: UnsafeRawPointer
}
</pre></div>
<p>然后,我们去寻找typeDescriptor的类型,从TargetStructMetadata的源码中,我们找到typeDescriptor的类型是TargetStructDescriptor。我们去搜索TargetStructDescriptor的源码,得到了TargetStructDescriptor类的定义以及属性如下:</p>
<div class="jb51code"><pre class="brush:cpp;">class TargetStructDescriptor final: public TargetValueTypeDescriptor&lt;Runtime&gt;,
      public TrailingGenericContextObjects&lt;TargetStructDescriptor&lt;Runtime&gt;,
                TargetTypeGenericContextDescriptorHeader,
/*additional trailing objects*/
                TargetForeignMetadataInitialization&lt;Runtime&gt;,
                TargetSingletonMetadataInitialization&lt;Runtime&gt;,
                TargetCanonicalSpecializedMetadatasListCount&lt;Runtime&gt;,
                TargetCanonicalSpecializedMetadatasListEntry&lt;Runtime&gt;,
                TargetCanonicalSpecializedMetadatasCachingOnceToken&lt;Runtime&gt;&gt; {
    uint32_t NumFields;
    uint32_t FieldOffsetVectorOffset;
}
</pre></div>
<p>从源码中我们可以看到,TargetStructDescriptor继承自TargetValueTypeDescriptor,因此继承链和TargetEnumDescriptor一样,我们还原出来的TargetStructDescriptor的数据结构如下:</p>
<div class="jb51code"><pre class="brush:cpp;">struct TargetStructDescriptor{
    var flags: Int32
    var parent: TargetRelativeDirectPointer&lt;UnsafeRawPointer&gt;
    var name: TargetRelativeDirectPointer&lt;CChar&gt;
    var accessFunctionPointer: TargetRelativeDirectPointer&lt;UnsafeRawPointer&gt;
    var fieldDescriptor: TargetRelativeDirectPointer&lt;FieldDescriptor&gt;
    var NumFields: UInt32
    var FieldOffsetVectorOffset: UInt32
    ```
    func getFieldOffsets(_ metadata: UnsafeRawPointer) -&gt; UnsafePointer&lt;Int32&gt; {
            return UnsafeRawPointer(metadata.assumingMemoryBound(to: Int.self).advanced(by: numericCast(self.FieldOffsetVectorOffset))).assumingMemoryBound(to: Int32.self)
    }
    //参考handyjson 中
    var genericArgumentOffset: Int {
      return 2
    }
</pre></div>
<p>}</p>
<div class="jb51code"><pre class="brush:cpp;">此时`TargetStructMetadata`中的数据结构也可以修改一下
```swift
struct TargetStructMetadata{
    var kind: Int
    var typeDescriptor: UnsafeMutablePointer&lt;TargetStructDescriptor&gt;
}
</pre></div>
<p>接着我们还原FieldDescriptor的数据结构。我们先找到FieldDescriptor类的源码,找出它的属性,和方法,代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">class FieldDescriptor {
    const FieldRecord *getFieldRecordBuffer() const {
      return reinterpret_cast&lt;const FieldRecord *&gt;(this + 1);
    }
public:
    const RelativeDirectPointer&lt;const char&gt; MangledTypeName;
    const RelativeDirectPointer&lt;const char&gt; Superclass;
    FieldDescriptor() = delete;
    const FieldDescriptorKind Kind;
    const uint16_t FieldRecordSize;
    const uint32_t NumFields;
    llvm::ArrayRef&lt;FieldRecord&gt; getFields() const {
      return {getFieldRecordBuffer(), NumFields};
    }
}
</pre></div>
<p>我们来看一下getFields()方法,这个方法就是获取&nbsp;fields&nbsp;的方法,fields&nbsp;存的是&nbsp;<strong>FieldRecords</strong>,通过getFieldRecordBuffer()来读取<strong>FieldRecords</strong>。</p>
<p>在getFieldRecordBuffer()方法中,通过&nbsp;reinterpret_cast&nbsp;将&nbsp;(this + 1)&nbsp;强制转换成&nbsp;FieldRecord *&nbsp;类型。所以我们可以推测这个&nbsp;fields&nbsp;是一块连续的内存空间,这一块连续的内存空间存储的是&nbsp;<strong>FieldRecord</strong>&nbsp;类型,并且&nbsp;NumFields&nbsp;是它的容量大小。</p>
<p>在 C++ 中,&nbsp;this&nbsp;是一个指向该对象的指针,由于它是一个指针,因此它可以应用指针算术甚至数组索引。如果这个 this 是数组中的一个元素,(this + 1) 则将指向数组中的下一个对象。</p>
<p>所以我们可以把FieldDescriptor的数据结构还原成下面结构。</p>
<div class="jb51code"><pre class="brush:cpp;">struct FieldDescriptor {
    var MangledTypeName: TargetRelativeDirectPointer&lt;CChar&gt;
    var Superclass: TargetRelativeDirectPointer&lt;CChar&gt;
    var Kind: UInt16
    var FieldRecordSize:UInt16
    var NumFields: UInt32
    var fields: FieldRecordBuffer&lt;FieldRecord&gt;
}
</pre></div>
<p>其中FieldRecordBuffer我们可以把它还原成一个有连续空间的数组,容量为NumFields,存储的是FieldRecord。还原结构如下:</p>
<div class="jb51code"><pre class="brush:cpp;">struct FiledRecordBuffer&lt;Element&gt;{
    var element: Element   
    mutating func buffer(n: Int) -&gt; UnsafeBufferPointer&lt;Element&gt; {
      return withUnsafePointer(to: &amp;self) {
            let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
                return start
            }
            return UnsafeBufferPointer(start: ptr, count: n)
      }
    }
   
    mutating func index(of i: Int) -&gt; UnsafeMutablePointer&lt;Element&gt; {
      return withUnsafePointer(to: &amp;self) {
            return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
      }
    }
}
</pre></div>
<p>最后我们看一下FieldRecord的源码,得到的源码属性如下:</p>
<div class="jb51code"><pre class="brush:cpp;">class FieldRecord {
    const FieldRecordFlags Flags;
public:
    const RelativeDirectPointer&lt;const char&gt; MangledTypeName;
    const RelativeDirectPointer&lt;const char&gt; FieldName;
}
</pre></div>
<p>还原FieldRecord得到的数据结构如下:</p>
<div class="jb51code"><pre class="brush:cpp;">struct FieldRecord {
    var Flags: UInt32
    var MangledTypeName: TargetRelativeDirectPointer&lt;CChar&gt;
    var FieldName: TargetRelativeDirectPointer&lt;CChar&gt;
}
</pre></div>
<p>至此,我们把结构体的MetaData结构都还原出来。</p>
<p class="maodian"></p><h3>获取结构体的属性</h3>
<p>现在我们来验证一下结构体,获取结构体的属性。代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">@_silgen_name("swift_getTypeByMangledNameInContext")
func swift_H_getTypeByMangledNameInContext(typeName: UnsafeRawPointer, len: Int, context: UnsafeRawPointer, generic: UnsafeRawPointer) -&gt; UnsafeRawPointer
protocol BridgeProtocol {
}
extension BridgeProtocol {
    static func get(from pointer: UnsafeRawPointer) -&gt; Any {
      pointer.assumingMemoryBound(to: Self.self).pointee
    }
}
struct BridgeProtocolMetadata {
    let type: Any.Type
    let witness: Int
}
func customCast(type: Any.Type) -&gt; BridgeProtocol.Type {
    let container = BridgeProtocolMetadata(type: type, witness: 0)
    let cast = unsafeBitCast(container, to: BridgeProtocol.Type.self)
    return cast
}
struct LGStudent {
    var age = 18
    var name = "FWJ"
    let money = 2000
}
var t = LGStudent()
let ptr = unsafeBitCast(LGStudent.self as Any.Type, to: UnsafeMutablePointer&lt;TargetStructMetadata&gt;.self)
print("----------开始解析---------------")
let namePtr = ptr.pointee.typeDescriptor.pointee.name.getmeasureRelativeOffset()
let filedNum = ptr.pointee.typeDescriptor.pointee.NumFields
print("当前结构体的名称: \(String(cString: namePtr))")
print("当前结构体的属性数量 \(filedNum)")
print("============开始解析属性============")
let offsets = ptr.pointee.typeDescriptor.pointee.getFieldOffsets(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
for i in 0..&lt;filedNum {
    let fieldRecord = ptr.pointee.typeDescriptor.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i))
   
    let fieldOffset = offsets
    let fieldName = fieldRecord.pointee.FieldName.getmeasureRelativeOffset()
    print("--- \(String(cString: fieldName)) 属性信息 ---")   
    let mangledTypeName = fieldRecord.pointee.MangledTypeName.getmeasureRelativeOffset()
    print("mangledTypeName: \(String(cString: mangledTypeName))")
   
    let typeNameLength = Int(256)   
    let genericVector = UnsafeRawPointer(ptr).advanced(by: ptr.pointee.typeDescriptor.pointee.genericArgumentOffset * MemoryLayout&lt;UnsafeRawPointer&gt;.size).assumingMemoryBound(to: Any.Type.self)
   
    **let** fieldType = swift_H_getTypeByMangledNameInContext(typeName: mangledTypeName, len: typeNameLength, context: UnsafeRawPointer(ptr.pointee.typeDescriptor), generic: genericVector)
    let type = unsafeBitCast(fieldType, to: Any.Type.self)
    print("fieldType: \(type)")   
    let brigeProtocolType = customCast(type: type)
   
    let instanceAddress = withUnsafePointer(to: &amp;t) {
      return UnsafeRawPointer($0)
    }
      
    let fieldValue = brigeProtocolType.get(from: instanceAddress.advanced(by: Int(fieldOffset)))
    print("fieldValue: \(fieldValue)")
    print("--- \(String(cString: fieldName)) 属性信息 ---")
}
//打印结果
----------开始解析---------------
当前结构体的名称: LGStudent**
当前结构体的属性数量 3
============开始解析属性============
--- age 属性信息 ---
mangledTypeName: Si
fieldType: Int
fieldValue: 18
--- age 属性信息 ---
--- name 属性信息 ---
mangledTypeName: SS
fieldType: String
fieldValue: FWJ
--- name 属性信息 ---
--- money 属性信息 ---
mangledTypeName: Si
fieldType: Int
fieldValue: 2000
--- money 属性信息 ---
</pre></div>
<p class="maodian"></p><h4>swift_getTypeByMangledNameInContext 函数</h4>
<p>这个函数的源码在MetadataLookup.cpp文件中,我们来看一下它的具体实现</p>
<div class="jb51code"><pre class="brush:cpp;">SWIFT_CC(swift) SWIFT_RUNTIME_EXPORT
const Metadata * _Nullable
swift_getTypeByMangledNameInContext(
                           const char *typeNameStart,
                           size_t typeNameLength,
                           const TargetContextDescriptor&lt;InProcess&gt; *context,
                           const void * const *genericArgs) {
    llvm::StringRef typeName(typeNameStart, typeNameLength);
    SubstGenericParametersFromMetadata substitutions(context, genericArgs);
    return swift_getTypeByMangledName(MetadataState::Complete, typeName,
                              genericArgs,
                              [&amp;substitutions](unsigned depth, unsigned index) {
                                    return substitutions.getMetadata(depth, index);
                              },
                              [&amp;substitutions](const Metadata *type, unsigned index) {
                                    return substitutions.getWitnessTable(type, index);
                              }).getType().getMetadata();
}
</pre></div>
<p>这个函数返回的是Metadata类型的指针,也就是Swift函数中的Type类型。可以通过这个函数获取到每个函数的类型。但是这个函数是C++函数,需要把它转换成Swift函数。这里参考了HandyJSON这个第三方库,使用@_silgen_name映射成swift函数。具体代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">@_silgen_name("swift_getTypeByMangledNameInContext")
func swift_H_getTypeByMangledNameInContext(typeName: UnsafeRawPointer, len: Int, context: UnsafeRawPointer, generic: UnsafeRawPointer) -&gt; UnsafeRawPointer
</pre></div>
<p class="maodian"></p><h4>获取属性的值</h4>
<p>我们可以根据类型和&nbsp;FieldOffsetVectorOffset属性值存储相对于实例的偏移量获取属性值,获取属性值存储的指针。参考&nbsp;HandyJSON&nbsp;通过协议中Self&nbsp;代表真实的类型去读取指针的值。代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">protocol BridgeProtocol {
}
extension BridgeProtocol {
    static func get(from pointer: UnsafeRawPointer) -&gt; Any {
      pointer.assumingMemoryBound(to: Self.self).pointee
    }
}
struct BridgeProtocolMetadata {
    let type: Any.Type
    let witness: Int
}
func customCast(type: Any.Type) -&gt; BridgeProtocol.Type {
    let container = BridgeProtocolMetadata(type: type, witness: 0)
    let cast = unsafeBitCast(container, to: BridgeProtocol.Type.self)
    return cast
}
</pre></div>
<ul><li>这个函数传入一个Any.Type的类型,通过它来创建一个协议的&nbsp;Metadata结构相同的BrigeProtocolMetadata实例</li><li>通过&nbsp;BrigeProtocolMetadata转换成协议的&nbsp;BrigeProtocol.Type.self,也就是协议的&nbsp;Metadata,那么此时这个协议类型可以获取到属性的真实类型</li><li>将属性值指针转换为&nbsp;Self类型的类型指针,通过pointee就可以获取真实的值</li></ul>
<p>以上就是Swift进阶教程Mirror反射示例详解的详细内容,更多关于Swift进阶Mirror反射的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Swift实现表格视图单元格多选</li><li>Swift自定义UITableViewCell背景色</li><li>swift实现颜色渐变以及转换动画</li><li>swift实现随机背景色</li><li>Swift Set集合及常用方法详解总结</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Swift进阶教程Mirror反射示例详解