背景

Swift 5 出了,主要是 ABI 稳定了,从 ABI Dashboard 来看为了解决 ABI 稳定问题,对 type metadata 也有不少改动。众所周知,我们 App 的 JSON 库 HandyJSON 是强依赖 metadata 结构的,如果 metadata 有大规模的改动可能直接导致这个库完全不能用,本着早发现早治疗的心态我赶快下载了 Xcode 10.2 beta,一跑果然编不过了,没办法只好自己着手来解决问题了。

Metadata 的结构演进

为了便于理解,先画个图看一下 metadata 的具体结构,每一格代码一个指针长度,这是 64 位系统下的 metadata 结构,32 位系统下 nominal type descriptor 的偏移在 11 个指针长度的位置,官方文档里有详细的说明。

Swift type metadata 的结构其实并没有明显的变化,而其中的 nominal type descriptor 结构却经历了一系列的变化。

Swift 4.2 以前

在 Swift 4.2 (不包括 4.2)以前的结构是这样的:

![](https://user-gold-cdn.xitu.io/2019/2/26/16929595b0015018?w=490&h=734&f=png&s=61393)
struct _NominalTypeDescriptor {var mangledName: Int32var numberOfFields: Int32var fieldOffsetVector: Int32var fieldNames: Int32var fieldTypesAccessor: Int32
}
复制代码

nominal type descriptor 包含了属性的名字和访问属性的类型信息的函数,HandyJSON 最初的原理就是从 nominal type descriptor 中取得属性的类型信息然后把 JSON 字串里的相应值赋进去,由于 fieldTypeAccessor 符合 c 的 calling convention,把指针强转一下就能获得类型信息:

var fieldTypes: [Any.Type]? {guard let nominalTypeDescriptor = self.nominalTypeDescriptor else {return nil}guard let function = nominalTypeDescriptor.fieldTypesAccessor else { return nil }return (0..<nominalTypeDescriptor.numberOfFields).map {return unsafeBitCast(function(UnsafePointer<Int>(pointer)).advanced(by: $0).pointee, to: Any.Type.self)}
}
复制代码
Swift 4.2

Swift 4.2 对 nominal type descriptor 做了调整,struct 和 class 结构变得有所不同,乍看没有少什么东西,其实对 fieldTypesAccessor 这个函数做了修改,不再符合 c 的 calling convention,因此不可以再从 nominal type descriptor 获取类型信息。

struct _StructContextDescriptor: _ContextDescriptorProtocol {var flags: Int32var parent: Int32var mangledName: Int32var fieldTypesAccessor: Int32var numberOfFields: Int32var fieldOffsetVector: Int32
}struct _ClassContextDescriptor: _ContextDescriptorProtocol {var flags: Int32var parent: Int32var mangledName: Int32var fieldTypesAccessor: Int32var superClsRef: Int32var reservedWord1: Int32var reservedWord2: Int32var numImmediateMembers: Int32var numberOfFields: Int32var fieldOffsetVector: Int32
}
复制代码

尽管苹果希望我们用 Mirror 来做反射,但是其实 Mirror 至今为止都不包含属性的类型的信息,因此苹果留了一个临时接口 swift_getFieldAt 来帮助我们获取类型信息:

@_silgen_name("swift_getFieldAt")
func _getFieldAt(_ type: Any.Type,_ index: Int,_ callback: @convention(c) (UnsafePointer<CChar>, UnsafeRawPointer, UnsafeMutableRawPointer) -> Void,_ ctx: UnsafeMutableRawPointer
)
复制代码

为什么说是临时的呢,因为 Swift 5 的时候就发现这个接口没了。。。。

Swift 5.0

到了 Swift 5.0 的时候,前面已经说过了获取类型的那个接口没了,那么我们只好翻出 Swift 的源码来找找思路了,
找到 TypeContextDescriptorBuilderBase 类的 layout() 方法:

void layout() {asImpl().computeIdentity();super::layout();asImpl().addName();asImpl().addAccessFunction();asImpl().addReflectionFieldDescriptor();asImpl().addLayoutInfo();asImpl().addGenericSignature();asImpl().maybeAddResilientSuperclass();asImpl().maybeAddMetadataInitialization();
}
复制代码

按源码写出 nominal type descriptor 的结构如下:

struct _StructContextDescriptor: _ContextDescriptorProtocol {var flags: Int32var parent: Int32var mangledNameOffset: Int32var fieldTypesAccessor: Int32var reflectionFieldDescriptor: Int32var numberOfFields: Int32var fieldOffsetVector: Int32
}struct _ClassContextDescriptor: _ContextDescriptorProtocol {var flags: Int32var parent: Int32var mangledNameOffset: Int32var fieldTypesAccessor: Int32var reflectionFieldDescriptor: Int32var superClsRef: Int32var metadataNegativeSizeInWords: Int32var metadataPositiveSizeInWords: Int32var numImmediateMembers: Int32var numberOfFields: Int32var fieldOffsetVector: Int32
}
复制代码

虽然 fieldTypesAccessor 还是无法调用,但是我们发现这里多了一个 reflectionFieldDescriptor 指针,直觉告诉我办法应该在这个东西里面,所以先看下这个东西是什么结构:

void addReflectionFieldDescriptor() {....B.addRelativeAddress(IGM.getAddrOfReflectionFieldDescriptor(getType()->getDeclaredType()->getCanonicalType()));
}
复制代码

逻辑基本就是拿到 ReflectionFieldDescriptor 的地址,然后把地址放到相应的内存里,需要注意的是这里放的是一个相对的地址,RelativePointer 的注释中写道:

// A reference can be absolute or relative: // //   - An absolute reference is a pointer to the object. // //   - A relative reference is a (signed) offset from the address of the //     reference to the address of its direct referent.

相对引用指的是相对当前引用指针地址的偏移量,于是我们有了获取 ReflectionFieldDescriptor 地址的方法:

var reflectionFieldDescriptor: FieldDescriptor? {guard let contextDescriptor = self.contextDescriptor else {return nil}let pointer = UnsafePointer<Int>(self.pointer)let base = pointer.advanced(by: contextDescriptorOffsetLocation)let offset = contextDescriptor.reflectionFieldDescriptorlet address = base.pointee + 4 * 4 // (4 properties in front) * (sizeof Int32)guard let fieldDescriptorPtr = UnsafePointer<_FieldDescriptor>(bitPattern: address + offset) else {return nil}return FieldDescriptor(pointer: fieldDescriptorPtr)
}
复制代码

拿到了地址,我们还需要知道 FieldDescriptor 这个结构是什么样子的,我们找到 FieldDescriptor 这个类:

// 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);}const RelativeDirectPointer<const char> MangledTypeName;const RelativeDirectPointer<const char> Superclass;public:FieldDescriptor() = delete;const FieldDescriptorKind Kind;const uint16_t FieldRecordSize;const uint32_t NumFields;using const_iterator = FieldRecordIterator;....
}
复制代码

FieldDescriptor 的结构里有一个 FieldRecord 的数组,从名字看里面应该保存了类型信息,我们再翻出 FieldRecord 的源码:

class FieldRecord {const FieldRecordFlags Flags;const RelativeDirectPointer<const char> MangledTypeName;const RelativeDirectPointer<const char> FieldName;....
}
复制代码

很遗憾 FieldRecord 并没有直接保存类型信息,只有一个 MangledTypeName ,问题不大,我们还有一个叫 swift_getTypeByMangledNameInContext 的函数,这个函数背后调用的 swift_getTypeByMangledName 函数与之前的 getFieldAt 内部调用的是同一个函数,返回是 Any.Type:

@_silgen_name("swift_getTypeByMangledNameInContext")
public func _getTypeByMangledNameInContext(_ name: UnsafePointer<UInt8>,_ nameLength: Int,genericContext: UnsafeRawPointer?,genericArguments: UnsafeRawPointer?)-> Any.Type?
复制代码

至此我们解决了由 Swift 5.0 metadata 变动导致的灾难性编译问题,顺便把 metadata 结构梳理了一下,代码已经提交到了 dev_for_swift5.0 分支。

后记

Mirror 是官方支持的反射工具,使用 Metadata 这种办法算是一种非主流的做法,但是苹果也意识到 Mirror 里面有部分信息无法提供,据说是技术上有一点困难所以暂时没法把类型信息等放到 Mirror 里面,所以才在 Metadata 里增加了用于反射的信息,ABI Dashboard 里也说 ABI 稳定的优先级高于完整的反射功能,可见 Metadata 这一部分的结构暂时不会大改了,但是远期来看苹果还是会在 Mirror 里面完整支持反射功能。

相关资料

swift.org/abi-stabili…
github.com/apple/swift…
github.com/apple/swift…
github.com/alibaba/Han…

Swift 5 Type Metadata 详解相关推荐

  1. Swift - SwiftyJSON的使用详解(附样例,用于JSON数据处理)

    转自:http://www.hangge.com/blog/cache/detail_968.html Swift - SwiftyJSON的使用详解(附样例,用于JSON数据处理) 2016-01- ...

  2. 1.Type类型详解

    文章目录 1.Type类型 1.1 什么是Type? 1.2 Type的用处 1.3 ParmeterizedType(参数化类型)的作用 1.3.1 测试ParmeterizedType 1.3.2 ...

  3. Burp suite—Intruder中Attack Type模式详解

    Burp suite-Intruder中Attack Type模式详解 提要:Burp suite集成工具中的Intruder模块在日常的使用的安全测试中会经常频繁使用,Intruder中的攻击类型( ...

  4. 【input 标签的 type 属性详解】

    input 标签的 type 属性详解 1. input 输入标签的 type 属性 1.1 input 标签的 type类型 属性的常用属性值 ⑴ 单行文本框: type="text&qu ...

  5. 表单input标签type属性详解

    目标:详解表单input标签type属性常用的属性值 一.input标签和它的type属性 PS:input 元素可以用来生成一个供用户输入数据的简单文本框. 在默认的情况下, 什么样的数据均可以输入 ...

  6. Swift 5新特性详解:ABI 稳定终于来了!

    近日,苹果开发者博客更新了一篇关于Swift 5的文章,带来了Swift 5新特性的消息,其中最受开发期待的莫过于iOS 12.2将带来ABI 稳定性,这意味着基础库将植入系统中,不再包含在App中, ...

  7. Swift 中的关键字详解

    转载自:https://www.cnblogs.com/liYongJun0526/p/7522130.html 要学习Swift这门语言,就必须先了解Swift的关键字及对应的解释.这里就列一下在S ...

  8. oracle object_type,Oracle TYPE OBJECT详解 | 学步园

    ====================================================== 最近在自学PL/SQL高级编程,了解到对象类型(OBJECT TYPE). 特意搜索了一下 ...

  9. struts2中result的type属性详解

    (1)type="dispatcher" 为默认,用于jsp页面跳转  <result name="success">/index.jsp</ ...

最新文章

  1. 文本编辑BOM标记(Byte Order Mark)
  2. Git创建版本库详尽教程
  3. 「AI在左,营销在右」互动营销创意破圈,这事儿不难
  4. 市面上有哪几种门_目前市面上木门的几种分类
  5. openEuler Developer Day 启动大会招募环节,报名通道同步开启!
  6. 通过数据,从键盘录入学生考试科目数,然后依次录入学的每一科分数.使用数组存储学生分数.然后输出总分,平均分,简单易理解
  7. windows下svn server的安装和配置
  8. 开发日记(01) - uni-app 使用等宽字体对齐数字宽度
  9. 居住证服务器维护需要多长时间,居住证正在制证要多久能拿到
  10. 小白入门之HTML--第四章 CSS样式深入
  11. 以华为2016年笔试题为例,详解牛客网的在线判题系统(OJ模式)
  12. bzoj 2298 problem a
  13. 一篇博客解决网线挑选问题
  14. Java 添加和删除Word文档水印
  15. V2X-Hub,车路协同云控平台
  16. CH344是一款USB总线的转接芯片
  17. 令人头疼的背包九讲(1)0/1背包问题
  18. 20款漂亮免费经典国外英文复古字体
  19. Python实战 | 如何抓取tx短片弹幕并作词云图分析
  20. 计算机考上研究生暑假去哪里实习_大三计算机专业学生怎么找实习?

热门文章

  1. 车联网行业No.1元征科技的云端架构实现 1
  2. ubuntu设置网卡默认启动_Ubuntu18.04 配置网卡上网
  3. 电商物流系统技术架构进化史
  4. 静态通讯录+动态版通讯录
  5. 十五分钟逻辑学系列之二——逻辑思维的基本规律
  6. 《荒漠甘泉》4月19日
  7. unity ugui android 小键盘,Unity inputfield 实现显示 隐藏密码功能(在安卓中切换不打开下虚拟键盘)...
  8. 《啊哈算法》第四章 万能的搜索
  9. 列维飞行的幂律意味着什么
  10. kwgt 歌词_kwgt桌面插件美化下载-Eight for kwgt专业版主题包v3.9.136.1 最新版-腾飞网...