拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SE-0369:为 AnyKeyPath 添加 CustomDebugStringConvertible 一致性,让 KeyPath 的调试输出可读

SE-0369为 AnyKeyPath 添加 CustomDebugStringConvertible 一致性让 KeyPath 的调试输出可读【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolution导读Swift 的 KeyPath 是编译器生成、用于描述如何从根类型取到某个值的类型安全引用。但在 Swift 5.8 之前把 KeyPath 交给print()或 LLDB 的po命令只会得到Swift.KeyPathTheme, Color这样的类默认描述无法区分它到底指向哪个属性。SE-0369已实现于 Swift 5.8为AnyKeyPath补充了CustomDebugStringConvertible一致性让调试输出尽量还原源码中\Theme.backgroundColor的写法并在元数据或符号缺失时退化为带偏移量/地址的类型化描述。本文以 SE-0369 提案 为骨架结合仓库中 KeyPath 的演进历史SE-0161、SE-0210、SE-0227与标准库相关机制完整梳理其动机、实现设计、退化路径、ABI 影响与替代方案。提案背景为什么 KeyPath 的调试输出是个问题KeyPath 系列类型由 SE-0161 Smart KeyPaths 在 Swift 4.0 引入取代了仅限 Darwin 平台、只适用于NSObject、且会丢失类型信息的#keyPath()字符串方案。KeyPath 是未调用的属性引用可以用\Type.path或类型推断形式的\.path写出支持属性、下标、可选链等组合。SE-0161 定义了一族逐级具体的类AnyKeyPath完全类型擦除表示任意根类型上的任意路径很多操作在运行时可能失败因此返回 OptionalPartialKeyPathRoot已知根类型未知路径KeyPathRoot, Value根类型与值类型都已知WritableKeyPathRoot, Value/ReferenceWritableKeyPathRoot, Value分别表示值语义与引用语义的可写路径。值得注意的是SE-0161 的原始设计见 0161-key-paths.md 第 78 行里AnyKeyPath就已经声明为CustomDebugStringConvertible但直到 SE-0369 之前这个一致性并没有真正产出有用的内容——打印一个 KeyPath 得到的只是普通 Swift 类的默认debugDescriptionSwift.KeyPathTheme, Color之类。SE-0369 的动机非常具体给定下面这个结构体struct Theme { var backgroundColor: Color var foregroundColor: Color var overlay: Color { backgroundColor.withAlpha(0.8) } }print(\Theme.backgroundColor)的输出大致是Swift.KeyPathTheme, Color这种输出完全无法把foregroundColor与其他Theme属性区分开。理想的输出应当与源码中书写形式完全一致\Theme.backgroundColor这正好呼应 SE-0161 中提到的让间接引用暴露属性的元数据的目标KeyPath 本身在二进制中携带了足够的信息内存偏移、getter 符号、类型元数据只是此前没有把这些信息整理成人类可读的形式。目标输出形态SE-0369 提出debugDescription应尽量利用二进制中可用的任何信息最佳情况输出与源码书写一致的\Theme.backgroundColor数据缺失的退化情况输出其他仍有诊断价值的类型化信息详见下文缺失数据时的退化输出。这与CustomDebugStringConvertible的定位一致——该协议在 SE-0041 转换协议命名约定 中被定义为转换为协议名所含类型的协议debugDescription面向调试场景不承担正式 API 的格式稳定性承诺。详细设计如何从 KeyPath 的内部缓冲还原属性名KeyPath 在内存中由一段段的段segment组成。提案给出的实现思路与标准库KeyPath.swift中现有的_project系列函数如出一辙遍历 KeyPath 的内部缓冲逐段处理。每种段类型采用不同的还原策略段类型还原策略对应机制偏移段stored property从反射元数据取属性名_getRecursiveChildCount、_getChildOffset、_getChildMetadata可选链 / 强制解包段追加硬编码的?或!无额外查找计算属性段从ComputedAccessorsPtr取getter()调用swift::lookupSymbol()反查符号并 demangle运行时符号查找对于偏移段提案明确指出所用的_getRecursiveChildCount/_getChildOffset/_getChildMetadata正是Mirror今天在用的同一套反射机制——也就是说这段还原逻辑与标准库反射功能共享底层元数据。需要的两处运行时改动要实现对计算属性段的描述SE-0369 需要向 Swift 运行时runtime提两项能力暴露一个 Swift 调用约定的函数用于调用swift::lookupSymbol()实现并暴露一个用于 demangle KeyPath 函数名的函数——而且要去掉现有 demangling 函数会附加的各种装饰信息ornamentation只留下属性名。这里的背景是KeyPath 对计算属性的访问是经由ComputedAccessorsPtr中的 getter/setter 函数指针完成的。反过来拿到函数指针后通过swift::lookupSymbol()反查符号名再 demangle 即可还原出属性名。这与 SE-0161 中KeyPath 封装了类型、可变性、属性名与取值/赋值能力的表述一致——属性名信息并不总以字符串形式内嵌在 KeyPath 中而是可以从符号层面重建。缺失数据时的退化输出提案明确指出两个已知的数据缺失场景反射元数据未被生成目标使用-swift-disable-reflection-metadata标志编译符号被链接器剥离lookupSymbol()找不到目标符号名。对应的退化输出如下偏移段退化为offset [x] ([typename])其中x是从反射元数据读到的内存偏移typename是被返回值的类型print(\Theme.backgroundColor) // outputs \Theme.offset 0 (Color)注意offset信息本就是 KeyPath 为存储属性编码的既有能力——SE-0210 就是通过MemoryLayout.offset(of:)暴露同一份信息且明确指出KeyPath 对象已经为存储属性编码了实现该功能所需的偏移信息。lookupSymbol失败退化为computed 0xABCDEFG (typename)此时打印内存中的地址十六进制加类型名print(\Theme.overlay) // outputs \Theme.computed 0xABCDEFG (Color)提案还点出了这里的工程权衡把内存地址与函数名关联起来可能很困难因此附上类型名能提供额外上下文便于诊断。对源码兼容性、ABI 与 API 弹性的影响源码兼容性自行扩展AnyKeyPath来实现CustomDebugStringConvertible的程序将无法再编译作者需要删除该一致性声明。提案基于 GitHub 搜索判断当时没有公开的 Swift 项目这么做print作用于 KeyPath 的输出结果当然会与以前不同提案判断现有生产代码不太可能依赖旧输出即使有单元测试断言旧输出问题也容易定位修复且新输出通常让测试更易读。ABI 稳定性提案会在标准库 ABI 中新增一个属性和协议一致性并做相应的可用性availability保护新的调试输出不会 backdeploy运行在更老 ABI 稳定版本 OS 上的 Swift 程序无法依赖新输出格式。API 弹性debugDescription的实现可能在初始工作完成后继续演进输出格式不被承诺为稳定。提案预判了几类可能的后续变化编译器新增功能可能带来新的二进制元数据可资利用例如KeyPath 段到人类可读名/稳定标识符的查找表KeyPath 新增功能时输出需要同步反映。提案特别举例_forEachFieldWithKeyPath产生的 KeyPath 是不完整的——它们只是在内存偏移处设值不会触发didSet观察器。若该函数将来被公开把这一语义差异反映进调试信息将很有价值下标打印行为可能调整比如总是打印下标参数的值、仅在输出较短时打印或从.subscript()改为[]风格Swift 语言工作组可能出台关于调试描述的新政策本函数的输出需要随之更新。被否决的替代方案及其权衡SE-0369 完整记录了三个被认真考虑过的替代方案理解它们有助于把握为什么最终选择反射元数据 符号反查路线1. 打印完全限定名或附加更多信息例如\ModuleName.MyType.myField、KeyPathMyType, MyFieldType \ModuleName.MyType.myField、(writable) \Theme.backgroundColor等。被否决理由这仅是调试用途当前提供的信息足以消除歧义调试时若真遇到歧义用户跑po myKeyPath \MyType.myField逐一比对即可锁定目标。2. 让 KeyPath 内嵌字符串描述这是最显然的方案——编译器本来就已经为 KeyPath 生成_kvcStringKey-Value Coding 字符串实现起来很容易而且 100% 可靠甚至可以作为实现description而非debugDescription的基础。但被否决的理由充分会增大编译产物体积可能到了不可接受的程度排除了未来打印下标型 KeyPath 参数的可能性这类 KeyPath 可在运行时动态创建字符串无法预先内嵌会拖慢 KeyPath 拼接appending操作因为字符串也要随之拼接。备选的输出额外元数据函数名→名称查找表方案同样被否决需要在编译器侧做大量工作收益却相对有限。提案作者还给出一个重要观点多数想要这种字符串的用户真正想要的是拿它去构建别的东西比如可编码的 KeyPath。这类能力应当按 KeyPath 或按类型显式 opt-in提供这才更有用——具体到可编码 KeyPath 还能消除重大潜在安全问题——并且应允许用户配置字符串内容以保持与旧版程序的向后兼容。3. 让 KeyPath 函数全局化防止链接器剥离符号把 getter 等函数设为全局理论上能让符号反查更可靠甚至使实现description而非debugDescription变得可行。但代价是可能膨胀二进制体积、增加链接时间有安全隐患dlsym之类工具将能找到这些函数。提案作者自认对链接器及典型 Swift 构建如何剥离符号了解有限但认为某些 Swift 程序 IDE 里把它做成可选项或许有用——不过这超出了本提案范围。未来方向添加 LLDB formatter / summary这是对本提案的自然增强可能改善开发者体验因为调试器可用的调试元数据可能比二进制内可用数据更多。但实现难度不小提案给出两条路线在标准库实现可供 formatter 从 Python 调用的 KeyPath 公开反射 API——作者认为对 formatter 之外的潜在应用而言过度设计但若可实现为internal函数则更有吸引力formatter 直接解析 KeyPath 原始内存本质上是把debugDescription的代码复制一遍——作者基于在标准库之外解析 KeyPath 内存的个人经验判断这条路极其困难且不可持续因为 KeyPath 的内存布局并非 ABI 稳定。仅在 DEBUG 构建中让 KeyPath 函数全局化为了在 Windows、Linux 等使用 COFF 或类 ELF 格式的平台上让swift::lookupSymbol正常工作这可能成为必要手段。仓库中的相关演进线索本提案并非孤立存在仓库中的 KeyPath 演进谱系有助于理解其定位SE-0161 智能 KeyPathKeyPath 家族与\语法Swift 4.0SE-0210 KeyPath 偏移量MemoryLayout.offset(of:)暴露存储属性偏移与 SE-0369 偏移段还原共享同一份KeyPath 已编码偏移的事实SE-0227 恒等 KeyPath\.self指代整个输入值Swift 5.0SE-0418 方法 Sendable 推断KeyPath 字面量可推断为KeyPathUser, String Sendable说明 KeyPath 正在持续获得并发安全等新语义这正对应 SE-0369 API 弹性一节所说的KeyPath 新增功能时输出需同步反映。总结SE-0369 为 Swift 开发者带来了一项低调但高频受益的改进从 Swift 5.8 起print与 LLDB 中的 KeyPath 不再是一串不可读的类名而是尽可能还原源码书写的\Theme.backgroundColor。其实现核心是遍历 KeyPath 缓冲的各段存储属性段借助Mirror同源的反射元数据机制_getRecursiveChildCount/_getChildOffset/_getChildMetadata取名计算属性段通过swift::lookupSymbol()反查 getter 符号并 demangle 还原可选链等段直接追加?/!在元数据被禁用或符号被剥离时则退化为带偏移量或地址的offset x (Type)/computed 0x… (Type)输出。由于输出格式不作稳定性承诺、且不 backdeploy依赖精确输出的场景应将其视为调试辅助信息而非 API。【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门