该特性在 Swift 5.8 实现。提议项 SE-0369
现状
本篇提议目的是为了让AnyKeyPath
支持实现CustomDebugStringConvertible
协议。
先来看一下当前要解决的问题。
如果对print()
或者po
命令传递 keypath(key路径,下文均使用原 keypath 表述),会输出 Swift 类的标准信息。例如,我们先定义Theme
struct:
struct Theme {
var backgroundColor: UIColor
var foregroundColor: UIColor
var overlay: UIColor {
backgroundColor.withAlphaComponent(0.8)
}
}
再添加 keypath 执行 print(Theme.backgroundColor)
, 大致输出如下:
// Swift 5.7 真实输出:
Swift.WritableKeyPath<__lldb_expr_1.Theme, __C.UIColor>
结果的关键信息包含Theme
和UIColor
, 但其实你无法通过结果来区分当前属性是backgroundColor
和foregroundColor
,因为它们是同类型。所以我们期望理想的输出应该是类似这样的:
Theme.backgroundColor
这是本篇提议想要解决的问题。
提议方案
Swift 中如果为某个类型实现CustomDebugStringConvertible
协议的debugDescription
方法,那么可以获得对应二进制文件中的任何可用信息。在最好的情况下,这些信息能大致产生上述输出,在最坏的情况下,也会输出其他可能有用的信息。我们需要的目标对象属性就在这些信息中。
设计细节
实现 CustomDebugStringConvertible
协议
跟目前在KeyPath.swift
中实现的函数_project
非常相似,该函数将循环使用 keypath 的每个缓存区,按下列方式来处理每个段(即segment):
对于偏移段,处理原理很简单:使用_getRecursiveChildCount
, _getChildOffset
, _getChildMetadata
来获取属性的字符串类型名称,Mirror
应该也可以实现该功能;
对于可选链,强制解包等,函数会适当地硬编码增加 "?" 和 "!";
对于计算段,可以对ComputedAccessorsPtr
的getter
方法结果调用swift::lookupSymbol()
,来解析该结果并获取属性名。
Swift 运行时更改
为了实现计算段的描述,需要对 Swift 运行时做两处更改:
- 需要公开 Swift call-convention 函数,用来调用
swift::lookupSymbol()
; - 需要实现并暴露一个新函数来解析 keypath 函数,来代替现有的解析函数。
处理缺失数据
当前有两种已知情况下,源数据不可用:
- 构建 target 时,选择了
swift-disable-reflection-metadata
标识,导致类型元数据编译前没有提交; - 链接器去掉了我们正在查找符号名称。
这两种不可用的场景下,输出的信息分别如下:
偏移场景
结果是 <offset [x] ([typename])>
,x
从反射元数据中读取的内存偏移量,typename
是将会返回的类型。所以此时打印 keypath 函数,则会输出:
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 输出更详细的类型数据。