大多数浏览器和
Developer App 均支持流媒体播放。
-
Swift 新功能
Swift 现在是所有 Apple 平台上诸多主要框架选用的语言,包括 SwiftUI、RealityKit 和 Create ML。与我们一起回顾 Swift 5.0,并探索 Xcode 11 中新推出的 Swift 5.1。了解性能和安全性方面的最新改进。探索该语言的新功能,以及这些功能如何助力这些新框架的开发。
资源
相关视频
WWDC19
-
下载
早上好 欢迎来到 What's New in Swift 专场
今天 我们将讨论两个重要 且令人兴奋的 Swift 发布版本 Swift 5 是我们最近在三月份发布的版本 Swift 5.1 现在可以在 Xcode 11 中使用开发者预览版
有了这两个新的版本 我们进一步释放了 Swift 作为语言和技术的潜力 Apple 和你们所有人都可以 在此基础上进行开发
从 Swift 和 API 的 强大组合的形式中获益
现在 Apple 的操作系统 已经为第三方 App 提供了 共享的 Swift Runtime 并且用 Swift 写的二进制框架 现在已经可以部署 使用这些 Apple 做出了 仅用于 Swift 的跑马灯效果框架
此外 在 Xcode 中 对 Swift 包管理器的集成支持 使得 Swift 包可以直接 进入 App 开发的核心工作流中 同时 Swift 语言本身有了新的可供性 来构建精美丰富的 API 也有了用于表达 API 发展的新特性 对于该语言及其整个生态环境来说 这都是一个非常激动人心的时刻 那么让我们开始吧 首先我要多说几句二进制框架 特别是深入谈谈 促成这一发展的核心要素 那些要素就是 ABI 稳定性 及模块稳定性
ABI 就是 Application Binary Interface 它是一些规则 用来控制编译后的代码 如何在运行时进行交互的具体细节 就像是函数调用这样的细节 那么它究竟是如何工作的 参数中的值是如何从调用者传递给被调用者的呢 可用的元数据是什么 它在内存中又是如何排列的 所有这些细节都是 编译后的代码相互作用时所必须的 因此为了说明这个观点 假设你有一个用 Swift 写的程序 它是一个 App 可以是命令行工具 这并不是很重要 只要是可执行的 它使用的框架也是用 Swift 写的 而所有的这些已编译代码 是在一个运行过程中一起运行的 那么这个可执行文件 正在使用框架中的 API 它们必须能在运行时与彼此进行对话 那么 为了完成这个 它们必须有一个可兼容的 ABI 就像这两段代码是被分开进行编译的 但编译后的代码则需要一起共同工作 在 ABI 稳定性之前 我们唯一能保证的是 它们会有一个可兼容的 ABI 只要构建它们的编译器是相同的 之所以这样 是因为 我们真的是在发展 Swift 的核心基础要素 以确保所有我们 想要的构建块都被放在合适的位置上 以便在未来进行构建 那么 在 Swift 5 中我们已经明确了那些细节 现在的 Swift 已有了 ABI 稳定性 这意味着这两个组件不再需要用相同的编译器进行构建了 只要它们是用 Swift 5 编译器 或更新的版本进行构建的 第二个重要的要素 就是模块的稳定性 这是一个编译时的概念 如果你有一个 Swift 框架 使用该框架内的所有 API 那么这些就都是一个被称为模块的共享命名空间的一部分 当 Swift 编译器构建该框架时 它会生成该框架中的所有 API 的 Manifest 之后该框架的客户端就可以使用这些 API 该 Manifest 被称为 Swift 模块文件 那么如果我们回到这个例子 我们就假设我们正在编译程序 有一个源文件 它引用了该框架 接下来该编译器就会去读取模块文件 并获取可用的 API 但是这个模块文件里的细节真的很丰富 并且在很多方面 与编译器本身紧密耦合 所以我们就有和 ABI 稳定性相同的问题了 那么唯一的办法就是 这两块必须要用相同的编译器进行编译 在 Swift 5.1 里我们引入了一个新的补充 Manifest 它被称为 Swift 模块接口文件 它可以被框架用来提供 用户端可以使用的稳定接口 而如果你将它打开 它看起来就像 Swift 源代码 它也是被构建在源稳定性的概念上的 这个概念在 Swift 中很早就有了 那么有了这两个要素 你就可以得到可以部署 并与他人共享的 Swift 框架
那么我们有很多十分有趣的 关于 ABI 稳定性的细节 我们已经准备了 很长一段时间了 如果你有兴趣 了解更多的细节 我们今天就要向大家介绍 swift.org 是一个很好的资源 这是 Swift 开放源代码的项目的主页 里面有很多很棒的 关于 ABI 稳定性的博客文章
同时 如果你有兴趣 使用二进制框架来为你自己所用 比如与他人进行共享 我们会在这周晚些时候的 Swift 中的 Binary Frameworks in Swift 专场中 对这些进行讨论 届时我们还会讨论 当与他人共享框架时 你应该进行考虑的一些事项
那么二进制框架只是 API 中要为大家介绍的一个部分 另一个重要的部分就是 Swift 包 现在 Xcode 中已经 集成了 Swift 包管理器 它们是创建 App 的核心工作流的一部分 这周还会有两个关于 在 Xcode 中创建和使用包的专场 二进制框架和 Swift 包的结合 为我们提供了一组非常丰富的选项 来与他人共享 API
那么让我们换个话题 来谈谈性能 Swift 语言是一种 现代的 安全的 且高性能的编程语言 同时 ABI 稳定性 还带来了一些关键的性能优势
其中的一个性能优势 是因为在操作系统中有了 App 所共享的 Swift Runtime 这是三月份发布的 包括 macOS iOS tvOS watchOS 以及现在的 iPadOS 在这些发布版本中都有 一个共享的 Swift Runtime 第三方 App 和 Apple 原生 App 及系统中的所有东西都在使用 那么 它究竟是如何工作 又是在什么时候开始起作用呢 如果你的 App 是用 Swift 5 或更新的版本创建的 它就会在运行的时候 使用系统中的共享 Runtime
然而 你也许仍然需要将你的 App 构建并部署到较早发行的操作系统版本中 而那时的版本还没有共享 Runtime
在这种情况下 Xcode 会继续在你的 App 中 捆绑一个 Runtime 的副本 以便它可以继续在那些较老的操作系统版本上运行
但是 我们总是倾向于使用在操作系统中的副本 那么这样一来 当运行在较新的系统上时 这个 App 中的副本将会失效 作为优化 iOS App Store 将不提供 App 中的 Runtime 副本 当下载设备的操作系统包含 Runtime 时 为用户节约下载流量 这是一个非常重要的代码量优化 但在操作系统中拥有这个 Runtime 其真正的好处是 现在它可以作为操作系统本身的一部分 来进行优化 然后这些优化就可以转到 App 本身了
有一个重要的好处就是启动时间 那么让我们回到过去 一年前 我们讨论了 Swift 4.2
如果你有一个 Objective-C App 它什么都不做 它只是启动 然后什么都不做 而一个 Swift App 也是 同样没有功能 它就只是启动 使用 Swift 大约会有 5% 的额外时间消耗 因为我们需要处理 嵌入的 Runtime 但是如果你的 App 是用 Swift 5 编译器重新编译的 并运行在一个有共享 Runtime 的操作系统上 这个时间消耗就没有了 这个真的十分重要 我的意思是 该延时是 用户要开始使用你的 App 的时间 和他们真正开始体验它的时间之间的差别
我们进行优化的 另一个重要的领域 是进一步调整编译器编译后的代码 它进一步减少了 Swift App 的代码规模 这真的很像是微调 考虑 Swift App 中特定的模式 比如说字典在生成代码时 应该怎样表示 等等 我们只是要确保编译器的输出 针对这些用例进行了优化 通过各种各样的优化 我们看到代码规模能够减少 10% 如果使用 Swift 5.1 编译器的话 如果你使用 Optimize for Size 选项 那代码规模会减少 15% 那么这是一些非常重要的优化 我们也已经继续 对桥接的性能进行完善 通过桥接 我说的是 Swift 和 Objective-C 之间的桥接 这两种语言之间有着很强的互操作性
那么 Swift 和 Objective-C 都有我们所谓的通用的流通类型 它们被用在整个 API 空间里 比如 String 和 NSString Dictionary 以及 NSDictionary 而这些流通类型之间的互操作性 是 Swift 和 Objective-C 之间 互操作性的非常基础的部分 使用自己的流通类型的 Objective-C API 被重新映射到使用 Swift 流通类型的 Swift 中 现在的工作一个编译器 是一个编译器工作的组合 但同时还要使用到 Runtime 它在你将一个类型的值 传递为另一种类型 并跨越 API 边界时出现
这是我们进一步调整的一部分 作为 Swift 的一部分 现在是操作系统的一部分 比如现在 NSDictionary 和 Dictionary 之间的桥接速度 比之前快 1.6 倍
如果你正在传递一个 Swift 字符串给 Objecitive-C 而它作为 NSString 桥接而使用 并且是从 Objective-C 端使用它 那么它的操作就可以达到 15 倍之快 所有的这些小的好处加起来确实不少 因为这些类型 在整个 API 生态系统中都在使用 现在说到字符串 我们继续完善了它们的核心表达 我们做了一个重大的改变 在 Swift 5 中的字符串类型上 这是一个隐藏的变化 我们将字符串的 Unicode 表达 从 UTF-16 更改为 UTF-8 那么这完全是由性能驱动的 这个变化有很多丰富的细节 如果你有兴趣了解更多的相关内容 你可以去 swift.org 那里有一篇博文 讲解了实际的技术变化及其推动力量 那么在这里我要强调一些重要的事情
首先 我们将 Swift 创建为 一种你可以达到像 C 语言性能的语言
但其中的一个关键方面是 我们想要 Swift 对现有的 C API 生态系统有很强的互操作性 当 Swift 字符串使用 UTF-16 时 当你把一个字符串传递给 C API 的时候 你也许不知道 你需要进行的操作是 分配 副本和转码 只是为了把字符串放进 一个可以传递到 C 的兼容格式中 那么这就是很大的开销了 现在通过移动到 UTF-8 我们可以直接将一个 以 null 结尾的 UTF-8 字符串 传递给 C API 不需要分配和副本 且零开销
同时我们还能够 扩大字符串类型本身的优化 字符串有一个小的字符串优化 即如果该字符串中的字符数 大约是 15 个字符或者更少的话 我们就不需要一个单独的分配来获得 这些字符的有效荷载 我们可以直接将它打包到字符串值中 这真的是一个进步 在 Swift 5 中我们能够将这个优化扩展到 包括基本上所有的 Unicode 字符 不仅限于 ASCII 这就意味着 它现在可以应用到非罗马字符的语言中 我们在这样做的同时 也在性能层面上保持 NSStrings 和 String 之间 强大的互操作性
但是真正令人兴奋的是 这完全是关乎性能的 我已经说过好几次了
有一个很好的例子 就是 SwiftNIO 对于不熟悉它的人来说 SwiftNIO 是来自于 用于服务器的 Swift 它是一个用于构建网络协议和服务的跨平台框架 它为了速度而进行了调整
通过切换到 UTF-8 我们可以看到构建在 SwiftNIO 上的 网络服务器的吞吐量增加了 20% 那么这就只是文本处理的一个基准 而这恰恰与我们的想法类似 我们想让字符串成为一种 你可以用它来进行 高性能密集的字符串操作 但同时它对用户友好 且易于使用的类型 过一会我会把舞台交给 Anna 她会向大家主要介绍 Swift 5 和 Swift 5.1 中的语言变化 在此之前 我想再谈一些 核心工具和改进 及其中的开源项目的关键方面
现在 Swift 作为一个开源项目 已经不仅仅是一个项目上的 日常的工程工作了 而是关于 Swift 成为一个 更广泛且更多样化的 软件生态系统的一部分 那么举个例子 Swift 社群聚集在一起 为 Swift 创建了官方的 Docker 镜像 它们都被托管在 Docker Hub 中 而如果你在 Mac 上 安装 Docker 其实就是 敲几下键盘的事情 你就可以拥有一个 Docker 就是这个 Docker 镜像 同时拥有在你的 Mac 上 工作的 Docker Linux 容器 包括一个编译器和包管理器 这就是你需要的一切 之所以能这样做 是因为容器如今被视为 构建服务的一个 固有的部分
另一个重要的开源技术 就是 SourceKit 它是语义代码引擎 支持着很多 Xcode 的特性 如自动补全 跳转到定义 以及重构等等 这是我们继续完善的地方 我们想要使得 代码补全之类的结果更好
所以我们需要不断地迭代 但同时我们想要让它 更可靠也更鲁棒
我们所做的努力之一 作为今年开源项目的一部分 是为 SourceKit 构建一个 新的压力测试工具
它所做的就是用 IDE 可以发出的所有查询 连续击打 SourceKit 以快速找出 SourceKit 的问题 那么比如崩溃和断言 它为我们创造了 可重现的测试用例 这里还有一些变化的东西
我们相信用 Swift 为你们所有人创建 一流的工具 并将其投入我们自己的 工作流程的一部分 这就像是我们在享用 自己的理念 而像这样的努力 就是现在我们日常的 项目工程的一个核心部分
那么我想要谈一谈 一个面向未来的 对 SourceKit 的投入 那就是采用语言服务器协议 举这个之前出现过的例子 你可以用 Xcode 或者把这个图片概括成为 任何一种编辑器或工具 我的意思是 SourceKit 是开源的 它被设计为用于 构建工具的可重用组件
因此这样可以工作 对 但这真的是一种很老的模型 你可以假设那里有 各种各样的工具和编辑器 以及 IDE 它们想要 连接到各种不同的语言服务 因此 虽然它们能够直接 连接逻辑来与 SourceKit 进行对话 但这就都是相当临时的 你必须让每一个编辑器 连接到它自己的支持 同时它们必须理解 SourceKit 而且它们也必须了解 所有它们想要连接的其他服务 所以这不是一个可扩展的模型 但是像大多数 计算机科学中的问题一样 你都可以用一个间接层来解决 除了性能问题 已经出现了一种 行业标准解决方案 称为语言服务协议或 LSP 这个想法是 如果编辑器使用 LSP 这是一组标准的查询 而服务可以将其返回 你就可以直接将它们 混聚在一起
这是一个正在进行的积极的努力 也是一个开放源代码 你可以关注一下 但是要让你们了解一下 它的功能 这个动画显示的是 使用 SourceKit LSP 的代码补全支持 还有对其他各种 编辑器的支持 你可以在 GitHub 的页面上找到说明
正是像这样的投入 让我们感到非常的兴奋 因为 Swift 真的是 我们为通用计算 而构建的语言 对吗 它有巨大的潜力 这实际上是为了 让 Swift 真正蓬勃发展 在一个多样化的软件生态系统中 到这里 我要把舞台 交给我的同事 Anna Zaks 她会向大家分享 Swift 中的语言变化
谢谢你 Ted Ted 和大家分享了 项目和编译器方面的改进 现在让我来为大家介绍 我们在 Swift 语言 以及 Swift 5 和 Swift 5.1 中的 标准库上所作的改进
这些特性中有许多都在 继续完善语言和库的核心部分 同时为了与 Apple 今年推出的 几个主要的 Swift 框架 匹配一致 我们已经添加了能支持创建 更好的 Swift API 的特性 你们很多人都知道 Swift 语言经历了 开放的发展过程 你们在幻灯片上看到的 这些 SE 编号 与可以 Swift 发展网站上 找到的特性文档相对应
这是一个很棒的资源 如果你想要进一步了解这些特性 现在 让我来带你们 看看其中的一些特性 首先我要讲几个例子 是关于遗漏缺陷的弥补 许多人都喜欢 单表达式闭包语法的简单性 因此他们需要用单表达式 来编写一个返回 而函数 方法和下标 则像是不必要的负担 那么现在你可以在任何地方 使用简单的语法了 另一个缺陷是被 一个开源贡献者修复的 他就是 Alejandro Alonso 令人惊讶的是 他刚刚高中毕业 我们看看这个结构体 它便利地为它的 两个属性定义默认值 以前 你可以调用一个初始化 然后不传递参数 你也可以调用一个初始化 然后传递所有参数 但是你不可以 调用一个初始化 然后只传递一些参数 在 Swift 5 中 这个问题被修复了 一切都如你所愿地进行 编译器会为 所有这些情况生成初始化 另一个重要的领域是 高性能计算 在 Swift 5 中 标准库 增加了对 SIMD 即单指令多数据流 指令和类型的支持 这些通常用于 为图形编写底层性能敏感的代码 比如图像处理或 AR 事实上 我们今年发布的 新的 RealityKit 库 就正在使用这些类型 新的 SIMD 类型表示 固定大小的 SIMD 矢量 正如你所期望的 你可以在这里使用 标准库整型和浮点类型作为元素 让我来带你们了解一下 你可以用这些类型来做什么 你可以从数组字面量 初始化 SIMD 矢量 这里我们有大小为 4 的数组 实际上是两个大小为 4 的数组 而新的点运算符 允许你在这些矢量上 执行逐点运算 比如等式和比较 这里举个例子 我们正在检查 x 是否在每个点上 都比 y 点大 结果告诉我们 x 只在最后两点比 y 大 该结果被储存在 另一种叫 SIMDMask 的类型中 而 SIMDMask 类型上的点运算符 让你可以进一步操作 这些生成的掩码 举个例子在这里 我们在对之前的计算结果取反
Swift 5 还为你提供了 更多的文本操作表达能力 字符串内插已经 在 Swift 5 中被重新设计 新设计的速度和距离 达到之前的 1.7 倍 你可以自定义 所构建的内插 通过提供自己的助手 你可以用内插字符串 将你的类型初始化 来赋予字符串内插自定义的意义
Swift 从一开始 就可以支持字符串内插 如果你写一个反斜杠符号 后面跟着一些括号内引用 在一个字符串字面量中 编译器就会 执行该代码 并将值插入字符串 这一直是行得通的 但也会有一些限制
举个例子 用一个封闭在 NSLocalizedString 中的内插字符串 这样是行不通的
内插绘发生在转换之前 比如说在这里 字符串文件不包含 对字符串中 插入的整型的转换 因此 You have 10 apples 将无法被转换
相反 你首先需要做的是 创建 formatString
接着 将它本地化
然后 将值插入 已经本地化的字符串中
这就是用 UIKit 和 AppKit 来完成字符串本地化的正确方法
但是新的字符串内插设计 让我们更进一步 来设计出更有表现力的 API 比如 SwiftUI 框架中的文本 文本用于表示 SwiftUI 中的标签 我们想要将其本地化
我们来看看要怎么做 这里我们传递一个 内插字符串 给文本的初始化 而这里的技巧是 文本初始化不接受 字符串类型作为输入 而是接受另一种类型 它叫做 LocalizedStringKey 该类型被定义在 SwiftUI 框架内 因此 Swift 编译器 就将自定义的规则 应用到了字符串中 通过字符串内插协议 来处理该内插
一旦它知道使用哪个规则 编译器就会转换 这个字符串内插 用该自动生成的代码
我们用三步来理解它的作用 第一步 Swift 创建 一个实例给构建器 针对 LocalizedStringKey 这个实例会包含两个东西 分别是 formatKey 和一个变量数组
第二步 我们通过处理 内插的段来构建字符串 首先 我们有一个字符串字面量 我们将它添加给 formatKey
其次 在处理 quantity 时 分别恢复 formatKey 中的 格式说明符 和变量数组中的值
最后给 formatKey 添加另一段字面值
第三步 调用 LocalizedStringKey 的初始化器 这个时候你有了足够的信息 来将字符串合理地本地化
这样 SwiftUI 就可以使用 该语言特性将文本本地化 然后用户就可以读取信息 很酷吧 这个例子只是 粗浅地介绍了一点 字符串内插的使用 如果你像我们一样 对这个新特性感兴趣的话 那么你可以去阅读 ExpressibleByStringInterpolation 协议的文档
现在 我们来谈谈焦点 API 设计的一部分就是要 决定从你的 API 中排除什么 在 Swift 5.1 中 我们已经 专门针对返回的类型做了改进 虽然很重要的一点是 返回的类型表示 类型的性能 你的 API 的用户可以从中推断 但有时我们想要 将返回的内容抽象化
一个函数可以 在运行时返回多次 或者它总是返回相同的类型 但是该类型也许会透露 你的 API 的实现细节
且显示一些你的 API 的用户 不应该推出的东西 让我们来看看 Swift 提供给这些例子的选项
我们在例子中 使用简单的形状 API 那么正如你所期待的 我们这里有一个 Shape 协议 我们有定义如圆形 椭圆形 和正方形等基本图形的类型
同时我们还有这样的结构 可以操作图形来创建 图形的并集然后转换它们 看看这个 FaceShape 的例子 注意 这个 API 会根据你的图形的类型 也就是 faceType 来返回不同的类型 但它们都是符合 Shape 协议的 因此它是一个很好的例子 将协议类型用作我们的返回类型 那么 在这个例子中 我们来构建一个八角星 通过创建一个正方形 和转换后的正方形的并集 对吗
在这里声明具体的返回类型 就会将大部分实现细节 透露给客户 同时显示出这个 不必要的细节 会让 API 很难推出
但是 在这里使用一个 协议类型 Shape 也不太好 让我们看看这是为什么
当协议类型被返回时 这里并不能保证 从每个调用中 返回相同的类型给 API 除 Swift 的泛型外 它也给我们带来了 这些基础的限制 如果八角星有两个值 从相同的 API 但不同的两个调用中 返回到这个 API 它们可能就没有相同的类型 这样你就不能进行比较相等 返回的类型不能 有任何的关联类型 也不能有涉及到自身的需求
此外 丢失此类型标识 也可能让 某些编译器无法进行优化
Swift 5.1 引入了另外一个 叫做不透明结果类型的概念 这是一个伟大的进步 适用于已知返回相同具体类型的 API 但是有可能想要将这种类型 对用户隐藏起来
不透明结果类型 在这里写作 some Shape 它代表一个特定的 Shape 类型从这个 API 中返回
这种类型标识的保证 也让我们能够 在 API 的主体里 执行更强的类型检查 因此 如果你有几个返回了 不同类型的返回语句 编译器就会将其缓存 并提醒你修复该问题
不透明结果类型能够 在 Swift 5.1 中使用 你可以从文档中了解更多
注意 这个特性需要 新的 Swift Runtime 因此它只能在更新的 Swift 操作系统里工作 如果你要进行向后部署 你就可以使用这个特性 但你需要进行静态可用性检查 来保证它们的使用
现在我们来说说代码重用 及叫做属性包装器的新功能
访问属性的自定模式很常见 其中的一些模式 具有一流的语言支持 比如懒惰模式 但你也可能正在编写 你自己的自定包装器 也许你有一些 访问访问本地线程存储或写时拷贝 也许你使用计算属性 来储存用户默认值
我们一直编写自定的 get 和 set 但有时这个代码是重复的 例如在这里我有两个 指定用户默认值的属性 但这个代码的大多数 只是在复制粘贴 有了属性包装器 我们可以声明一种 指定访问模式的类型
我们将它命名为 UserDefaults
接着 我们告诉编译器 这个类型是特殊的 它的首要目的就是 包裹一个属性并指定其访问模式
这样做我们得到的就是 这个类型会允许我们 使用一个自定属性来声明 用户默认访问模式的属性
我们来仔细看看 有了这里的属性包装器 我们可以重写 之前那两个用户默认属性 写成这样 这样就没有重复了 非常干净明了 我需要做的就只是 添加自定属性 同时知道这些属性 仍被声明为布尔类型 因此你可以使用它们 像使用简单的布尔值一样
属性包装器使得我们可以 定义自定访问模式 而属性可以选择使用它们 只需要在其声明中 添加一个自定属性
我们寻求特定的工具 来解决特定的问题 它们在各自的领域都十分有用
同样 DSL 在程序员的生活中 也有着重要的作用 我们用它们来 查询数据库并构建图表 我们喜欢这种声明风格 我们可以简洁明了地 声明我们网页的布局 但是 它们也有不同
当我们每次使用其中一种时 都需要进行上下文切换 每种语言都有自己的语法和语义
它们都有各自独特的 强大的工具来支持它们 如果你缺失了一个 HTML 标记 如果你在一个 HTML 编辑器里 这是很容易解决的
但是 因为语法和语义 都被调整为特定的目的 支持它们的工具 也常常有特定的领域
因此当我们需要将 这些 DSL 整合进我们的项目时 通常没有什么好办法
在一些情况里 我们会 添加自定构建阶段 但通常我们会用这个解决方案
它看起来十分眼熟 这是一个表示 HTML 的 字符串字面值
我们完成了整合 却失去了工具的支持 编译器代码补全将这个 看作一个字符串 这里没有类型检查 对 Swift 编译器来说只是一堆文本 因此像忘记结束标记 这样的低级错误 直到运行时才会被注意到
我们想要使用这些 DSL 但我们也想要将它们 整合到我们的语言和工具中
在 Swift 5.1 中 我们 引入了在 Swift 中定义 嵌入式 DSL 的功能 我们来看看这个代码 它定义了一个 HTML 对象 我的一位同事只是为了娱乐 用这个新的 Swift 特性 在几个小时内 为 HTML DSL 提供了原型支持 在这里你可以看到 这个代码看起来像 Swift 但你又被 HTML 元素的定义所吸引 而你在这里可以看到 熟悉的 Swift 概念 比如闭包和方法调用 我们这里使用的是 Swift 程序中的变量 该工具将会确保 没有标签的缺失 同时提供语法的高亮显示和重构操作
我们的设想是 你不仅可以声明一个 元素列表 还可以使用 这样的 Swift 控制语句 就在这个 DSL 当中
好了
让我们来看看在后台 这是如何实现的 DSL 实现者添加了一个函数 来构建每一个 HTML 元素 而这些函数都是闭包函数 而这里有趣的部分是 这些闭包是特殊的 它们都有这个自定特性 即 @HTMLBuilder 这告诉了编译器 要使用 HTMLBuilder 类型 来处理这些闭包
我们来看看包含 DSL 代码的 闭包函数是如何转换为 一个正常的 Swift 闭包函数的
这个 DSL 闭包在做什么呢 它正在生成一批值 然而 这些值是未使用的 同时这里也没有 返回语句 那么为了让它工作 编译器将这个代码进行转换 通过先收集这些未使用的值 然后调用构建函数 将它们组合起来
这些函数都是由 HTMLBuilder 类型提供的 它是由你这个 DSL 作者编写的 同时它可以构建任何 适合你的 DSL 的对象 这里我们正在构建 HTML 因此它就会构建 HTML 对象
我们很高兴使用这个功能 我们用它来驱动 你会在新的 SwiftUI 中 用到的声明语法
这里有一个相关的例子 在 Swift UI 中使用 其自定 Swift DSL
这个特性可在 beta 1 中使用 我们很想看到 它将怎样让你从中受益 而你又会用它构建出怎样的 DSL
我们会马上在 Swift 论坛上 讨论该特性背后的这些细节 如果你有兴趣来 塑造这个特性的未来 或者其他的 Swift 特性 那么十分欢迎你的加入
总的来说 我所说谈到的许多改进 都会在我们今年 新发布的 Swift 框架之中 我们非常期待看到 你们将如何从中获益 让你们的 API 更有表现力 干净且易于使用 我们的同事将会做一个 关于现代 Swift API 设计的演讲 届时他们会与你们分享 在用这些特性构建 Apple 框架时 所学到的一些经验教训 那么今天就到这里了 参会愉快 谢谢大家 [掌声]
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。