Swift5.8 中 AnyKeyPath 支持 CustomDebugStringConvertible 协议

2022-11-10 15:43:36 浏览数 (2)

该特性在 Swift 5.8 实现。提议项 SE-0369

现状

本篇提议目的是为了让AnyKeyPath支持实现CustomDebugStringConvertible协议。

先来看一下当前要解决的问题。

如果对print()或者po命令传递 keypath(key路径,下文均使用原 keypath 表述),会输出 Swift 类的标准信息。例如,我们先定义Theme struct:

代码语言:Swift复制
struct Theme {

    var backgroundColor: UIColor
    var foregroundColor: UIColor
    
    var overlay: UIColor {
        backgroundColor.withAlphaComponent(0.8)
    }
}

再添加 keypath 执行 print(Theme.backgroundColor), 大致输出如下:

代码语言:Swift复制
// Swift 5.7 真实输出:
Swift.WritableKeyPath<__lldb_expr_1.Theme, __C.UIColor>

结果的关键信息包含ThemeUIColor, 但其实你无法通过结果来区分当前属性是backgroundColorforegroundColor,因为它们是同类型。所以我们期望理想的输出应该是类似这样的:

代码语言:Swift复制
Theme.backgroundColor

这是本篇提议想要解决的问题。

提议方案

Swift 中如果为某个类型实现CustomDebugStringConvertible协议的debugDescription方法,那么可以获得对应二进制文件中的任何可用信息。在最好的情况下,这些信息能大致产生上述输出,在最坏的情况下,也会输出其他可能有用的信息。我们需要的目标对象属性就在这些信息中。

设计细节

实现 CustomDebugStringConvertible 协议

跟目前在KeyPath.swift中实现的函数_project非常相似,该函数将循环使用 keypath 的每个缓存区,按下列方式来处理每个段(即segment):

对于偏移段,处理原理很简单:使用_getRecursiveChildCount, _getChildOffset, _getChildMetadata来获取属性的字符串类型名称,Mirror应该也可以实现该功能;

对于可选链,强制解包等,函数会适当地硬编码增加 "?" 和 "!";

对于计算段,可以对ComputedAccessorsPtrgetter方法结果调用swift::lookupSymbol(),来解析该结果并获取属性名。

Swift 运行时更改

为了实现计算段的描述,需要对 Swift 运行时做两处更改:

  1. 需要公开 Swift call-convention 函数,用来调用swift::lookupSymbol()
  2. 需要实现并暴露一个新函数来解析 keypath 函数,来代替现有的解析函数。

处理缺失数据

当前有两种已知情况下,源数据不可用:

  1. 构建 target 时,选择了swift-disable-reflection-metadata标识,导致类型元数据编译前没有提交;
  2. 链接器去掉了我们正在查找符号名称。

这两种不可用的场景下,输出的信息分别如下:

偏移场景

结果是 <offset [x] ([typename])>x从反射元数据中读取的内存偏移量,typename是将会返回的类型。所以此时打印 keypath 函数,则会输出:

代码语言:Swift复制
print(Theme.backgroundColor) // outputs "Theme.<offset 0 (UIColor)>"
lookupSymbol查找失败场景

这种情况下我们打印 16进制的内存地址,并加上类型名:

代码语言:Swift复制
print(Theme.overlay) // outputs Theme.<computed 0xABCDEFG (UIColor)>

这里内存地址和函数名称其实很难关联。类型名其实也无法提供准确信息,可能对其他的上下文信息提供有用处。

总结

Swift 5.8 源代码中为AnyKeyPath实现CustomDebugStringConvertible,支持 keypath 输出更详细的类型数据。

0 人点赞