尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Swift Snippets 完全指南:用 SE-0356 为 Swift 包编写可编译、可运行、可嵌入文档的示例代码

Swift Snippets 完全指南:用 SE-0356 为 Swift 包编写可编译、可运行、可嵌入文档的示例代码 Swift Snippets 完全指南用 SE-0356 为 Swift 包编写可编译、可运行、可嵌入文档的示例代码【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolution本指南以 Swift Evolution 提案 SE-0356《Swift Snippets》Swift 5.7 已实现为核心系统讲解 Swift 包中一种全新的示例代码形态——snippets它是短小、单文件、可被 Swift Package ManagerSwiftPM自动识别并像可执行目标一样构建与运行的程序同时又能以代码块形式嵌入 Swift-DocC 文档。读完本文你将掌握 snippets 的目录约定、// snippet.hide/// snippet.show与切片slice标记语法、Snippet文档指令、CLI 构建运行方式、snippetsDirectory配置以及 SwiftPM、SymbolKit、Swift-DocC、snippet-build 工具链背后的设计原理。为什么需要 snippets示例代码的两难人们在用代码演示一个想法或 API 时通常只有两种载体完整的示例工程sample projects包含构建配置、资源、多个源文件最终产出完整的 app。它适合展示某个具体、完整的场景但创建和维护成本很高容易退化成什么都往里塞的 kitchen sink 示例读者也难以快速找到真正有价值的部分。文档内嵌代码片段code listings几行代码印在文档正文的代码框里。虽然阅读体验好但有三个突出问题容易过时go stale代码被当作普通文本对待不会定期构建语言或 API 演进后常常无法编译缺少编辑器支持作者在面向写作的编辑器里输入代码没有语法高亮与内联报错出错概率更高趋向伪代码作者明知代码不会被真正运行于是把大量解释塞进正文产出对读者复制到自己项目毫无帮助的代码。snippets 正是为了填补这两者之间的空档而生。按照提案的粒度划分示例导向的文档谱系大致是API 参考与内联代码碎片通常不可编译出现在列表、索引或符号页链接中不具备组合性Snippets本文主题一个文件演示一个任务组合一个或多个 API跨不超过少数几个模块。理想情况下读者可以复制粘贴或把它当作起点。典型例子包括一个排序算法的实现、一个有趣的 SwiftUI 视图层级、在视图中显示 3D 模型的快速配方、演示 Swift Argument Parser 选项的 struct、使用自定义 coding keys 的 Codable 示例、Swift Algorithms Guides 里的许多示例甚至不少 StackOverflow 回答完整示例工程与教程组合一个或多个 API乃至多种技术或模块演示多个相互独立场景。snippets不打算取代前两种载体而是提供介于两者之间的第三种教学方式。核心设计单文件、完整程序、处处可用snippets 是包内独立的.swift文件可以被 SwiftPM 当作可执行目标构建并运行。它具备三方面特性单文件作者和读者都容易理解天然适合内联进文档完整可运行程序能够被测试、维护作为可直接复制使用的代码交付可访问包内全部代码snippet 自动依赖宿主包host package中声明的所有库目标因此可以自由import这些模块在保持 snippet 代码简洁的同时实现强大行为。提案还展望了snippet-only packages几乎完全由 snippets 组成的包的可能性比如以新方式组合 UI 元素的配方集逐 snippet 教授 Swift 语言教材练习集等。由于 snippets 能访问包内共享库即使强大的概念也能用易读的 snippet 演示。编写一个 snippet描述、隐藏标记与 presentation code基本结构一个 snippet 文件可能是这样// The first contiguous line comments // serve as the snippets short description. func someCodeToShow() { print(Hello, world!) } // snippet.hide func someCodeToHide() { print(Some demo message) } // Still hidden someCodeToHide() // snippet.show someCodeToShow()文件顶部的连续行注释是 snippet 的简介description以 Markdown 书写通常是简短的一段话会随 snippet 一起展示。// snippet.hide与// snippet.show用于在文档或未来支持 snippets 的工具中切换隐藏/显示让作者可以在运行时加入额外的演示逻辑同时保持最终文档展示的代码干净整洁。上述文件在文档中最终呈现的效果大致如下The first contiguous line comments serve as the snippets short description.func someCodeToShow() { print(Hello, world!) } someCodeToShow()经过隐藏标记解析后提取出的代码称为该 snippet 的presentation code展示代码。切片Slices一段代码多个代码块文档中的代码块往往构成连续的叙事先展示一段代码配以散文说明再展示下一段且后一段会引用前一段定义的变量。例如First, callsetup()to initialize the context:let context setup()Then, callrequest(_:)with the desired mode:context.request(.immediate)第二个代码块引用了第一个代码块中的context因此就编译而言snippet 由两个代码块组成。为了支持这种用法作者可以在单个文件中用标识符切片并在文档中按需引用// snippet.setup let context setup() // snippet.request context.request(.immediate)标记的完整形式为// snippet.IDENTIFIER其中IDENTIFIER必须是URL 兼容的路径组件URL-compatible path component以兼容 DocC 的链接解析逻辑。开启一个新切片会自动终止上一个切片对于不相邻的切片可以用// snippet.end显式结束当前切片// snippet.setup let context setup() // snippet.end // More code here... // snippet.request context.request(.immediate)还可以把 show/hide 标记与切片混合使用// snippet.setup let context setup() // snippet.hide // More code here... // snippet.show // snippet.request context.request(.immediate)目录约定与快速上手默认目录Snippets要开始为包添加 snippets首先在包的Sources、Tests目录旁创建一个Snippets目录然后直接放入.swift文件即可。文件的基名base filename必须彼此唯一。SwiftPM 会假定每个文件构成一个独立可执行目标像构建其他可执行文件一样为宿主平台构建并运行它们。开始之后一个包可能长这样 MyPackage Package.swift Sources Tests Snippets Snippet1.swift Snippet2.swift Snippet3.swift分组一层子目录当 snippets 数量增多时可以在Snippets下再建一层子目录来组织仅允许一层这不会影响下文介绍的 snippet 链接 MyPackage Package.swift Sources Tests Snippets Group1 Snippet1.swift Snippet2.swift Snippet3.swift Group2 Snippet4.swift Snippet5.swift覆盖目录位置snippetsDirectory与./Sources、./Tests类似用户可以通过给Package初始化器新增的可选参数snippetsDirectory覆盖./Snippets的位置。由于 snippet 目标不会在 manifest 中逐个声明该设置存在于包级别let package Package( name: MyPackage, snippetsDirectory: Examples, products: [ // ... ], dependencies: [ // ... ], targets: [ // ... ] )提示提案后续示例默认使用Snippets目录。即使覆盖了snippetsDirectory下面Snippet路径中的命名空间组件仍固定写作Snippets。在 Swift-DocC 文档中使用SnippetSwift-DocC或其他文档工具可以在散文式 Markdown 文件中导入 snippets。DocC 中在任何位置使用新的块指令Snippet即可展示 snippet 的描述与代码它只有一个必填参数pathSnippet(path: my-package/Snippets/Snippet1)path参数由三个部分组成my-package包名取自Package.swiftmanifestSnippets一个非正式命名空间用于将 snippets 与符号symbols、文章articles区分开无论上文snippetsDirectory是否被覆盖这一部分都保持为SnippetsSnippet1snippet 名称取自文件去掉扩展名后的基名。要插入某个切片加上可选的slice参数值对应源码中的标识符Snippet(path: my-package/Snippets/Snippet1, slice: setup)构建与运行SwiftPM 的 CLI 支持创建 snippets 后SwiftPM 可以像处理可执行目标一样构建、运行它们。snippet 目标默认不会随普通构建执行只有显式传入swift build --build-snippets时才会构建这与构建测试需要显式选择swift test或swift build --build-tests的语义一致。提案建议在所有 CI 构建流程中构建 snippets至少也要在构建文档时构建。常用命令swift build # Builds source targets as usual, # but excluding tests and snippets. swift build --build-snippets # Builds source targets, including snippets. swift build Snippet1 # Build the snippet, Snippet1.swift, as an executable. swift run Snippet1 # Run the snippet, Snippet1.swift.测试 snippets断言与前置条件snippet 演示的代码本身理应由测试覆盖但作者也可能希望在运行 snippet 时断言特定行为。提案没有采用 XCTest——因为 XCTest 断言无法在没有平台相关测试 harness 的情况下被收集和记录。snippets 本质上是可执行文件因此用asserts断言与 preconditions前置条件即可满足绝大多数场景同时让交互式与非交互式 snippets 并存、目前统一对待。关键是 snippets 必须能在 CI 与外部测试方案中可测试从而一步完成自动化。示例let numbers [20, 19, 7, 12] let numbersMap numbers.map({ (number: Int) - Int in return 3 * number }) // snippet.hide print(numbersMap) precondition(numbersMap [60, 57, 21, 36])这里把print与precondition放进// snippet.hide与文件末尾之间运行时会校验结果但文档展示的代码保持简洁。详细设计工具链如何支撑 snippetsSwift Package Manager自动发现目标SwiftPM 在构造包的可用目标集合时会按以下模式自动发现 snippet 文件./Snippets/**.swift*./Snippets/*/*.swift每个文件都会成为一类新的.snippet目标行为大体等同于现有可执行目标。允许一层子目录是为了在文件系统组织与snippet 相关资源通过相对路径非正式查找之间取得平衡。snippet 目标自动依赖宿主包中声明的所有库目标因此 snippets 可以自由import这些模块。将来为支持 snippet-only 包、组合两个独立包的示例或需要辅助库的包snippets 还能导入 manifest 中声明的依赖包里的库见未来方向。SymbolKitsnippet 成为符号snippets 通过Symbol Graph JSON传递给 DocC每个 snippet 成为一种符号。snippet 符号包含两条主要信息以符号文档注释形式携带的描述以及通过新 mix-inSnippet携带的展示代码public struct Snippet: Mixin, Codable { public struct Slice: Codable { public var name: String? public var language: String? public var code: String } public var slices: [Slice] }当 snippet 没有任何切片注释时上述模型由一个包含全部可见代码的 slice组成。Swift-DocC解析与渲染Swift-DocC 为支持 snippets 需要做三件事查找并注册符号图 JSON 中出现的新的 snippet mix-in。由于把 snippets 当作符号处理借助 SymbolKit 数据模型这一步基本是免费的支持新的Snippet指令用与符号链接相同的逻辑校验path与slice参数。这以新的Semantic实例形式实现public final class Snippet: Semantic, DirectiveConvertible { public static let directiveName Snippet // etc. }在RenderContentCompiler中把Snippet出现处转换为段落与代码块每种出现处产生如下内容若Snippet是切片只输出该切片的代码作为CodeBlock若Snippet不是切片输出经正常符号处理流程的 Markdown 文档注释一组块元素然后为每个 snippet 切片输出其代码作为CodeBlock。Swift DocC Plugin 与snippet-build工具Swift DocC Plugin 是一个 SwiftPM 命令插件command plugin用于为 SwiftPM 库与可执行文件构建文档其插件机制基础来自提案 SE-0303《Package Manager Extensible Build Tools》。为了把包的 snippet 信息转发给 DocC新增了一个工具snippet-build负责把.swift文件转换为 Symbol Graph JSON插件会在docc之前运行它。snippet-build以与 SwiftPM 相同的方式遍历Snippets目录结构查找.swift文件为每个文件在 Symbol Graph 中创建一个 snippet 符号条目并输出到指定目录。用法如下USAGE: snippet-build snippet directory output directory module name ARGUMENTS: snippet directory - The directory containing Swift snippets output directory - The directory in which to place Symbol Graph JSON file(s) representing the snippets module name - The module name to use for the Symbol Graph (typically should be the package name)通常不期望用户手动运行这个命令。使用 Swift DocC Plugin 时需要开启--enable-experimental-snippet-support特性开关。关于 Swift 插件依赖的说明由于 SwiftPM 插件会把依赖折叠进插件客户端的依赖图中为避免潜在的依赖循环或冲突snippet-build放弃了一些有用但次要的依赖Swift Argument Parser很多包都会依赖它因此snippet-build使用位置参数手工实现参数解析且预期用法长期不变Swift Syntax本可用于代码块分词但 DocC 已在Swift-DocC-Render项目中实现了语法高亮。这一依赖限制正是推动把 Symbol Graph 生成从.swift文件下沉到编译器调研的动因之一——那将与现有为库和可执行文件生成符号图的功能几乎相同的用法对齐。兼容性影响Source compatibility启用 snippets 的改动不破坏源码兼容ABI stability不影响 ABI 稳定性API resilience不影响 API 弹性。备选方案回顾提案还评估并拒绝了四种替代路径这些对比有助于理解 snippets 的定位文字编程式把 snippets 写进 Markdown把源码嵌入 Markdown 文档、再提取组装成合法 Swift 文件或包去构建运行测试需要自定义文件格式、隐藏 setup/测试代码、控制 import 等工程量大且难以与现有工具链集成。snippets 则以直接用现有工具链就能跑的.swift文件为核心天然便于分享甚至粘贴进 StackOverflow 回答把 snippets 放进文档注释会过度聚焦模块内 API 的文档化放弃跨模块组合、纯教育目的 snippet 包等更有价值的场景把 snippets 做成 playgroundplayground 已演变为更强大、更依赖自定义工具、也更复杂的形态通常独立成篇并自带支持文件而开源 Swift 的包已有承载多文件多资源目标的能力snippets 刻意保持简单.swift文件并与 SwiftPM、Swift-DocC 紧密集成让测试充当 snippetssnippets 不打算成为测试或直接来自测试虽然可自带断言校验行为测试与示例代码的写作语境通常不同。未来或许可以从测试等来源提取 snippets。未来方向每文件多个 snippets未来可以用源码中的 start/end 标记表达多个 snippet多文件 snippets需要多个文件构建的情况已由示例目标/工程承载大概率不是目标但对嵌入现有多文件项目的 snippets或许可以在构建时提取这很可能要求提取逻辑下沉到编译器构建时提取 snippetssnippet-build可能下沉到将模块转换为 Symbol Graph JSON 的SymbolGraphGen库从而复用共享实现与语义信息支持从库、单元测试、大型示例工程等不同来源提取 snippets构建文档时构建 snippets当前 Swift-DocC 渲染文档只需读取 snippet 源文件无需构建未来可由插件在生成文档前请求构建 snippets或在编译器构建 snippets 生成符号图时隐式完成snippet 依赖当前 snippets 自动依赖宿主包内库未来可增加声明外部依赖的能力支持专为演示其他包中一个或多个库而存在的包。总结SE-0356 将示例代码从不可编译的文档文本与笨重的完整工程之间解放出来一个.swift文件既是完整可运行的程序又能用// snippet.hide/// snippet.show和切片标记控制其在文档中的呈现还能通过Snippet(path:slice:)被 DocC 按需引用。配合 SwiftPM 的自动目标发现、swift build --build-snippets/swift run的 CLI 支持、snippet-build的符号图转换snippets 真正实现了写一次、处处读跑。这套约定自 Swift 5.7 起可用是编写高质量、可验证、可嵌入文档的 Swift 示例代码的推荐方式。【免费下载链接】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),仅供参考
返回列表