大多数浏览器和
Developer App 均支持流媒体播放。
-
Swift 的新功能
和我们一起了解 Swift 的更新。我们将为您介绍如何提高性能,探索更安全而且可扩展的 Swift 软件包,并且分享 Swift 并发的改进。我们还将向您介绍 Swift 正则表达式、更出色的泛型,以及语言中内置的其他工具,帮助您编写更灵活、更有表现力的代码。
资源
- Celebrating learning experiences from the 2021 Swift Mentorship Program
- Contribute to Swift
- Diversity in Swift
- Swift Mentorship Program
相关视频
WWDC22
-
下载
♪ 柔和乐器演奏的嘻哈音乐 ♪ ♪ Angela Laar:大家好 我是 Angela Becca Royal-Gordon:我是 Becca Angela:欢迎关注 Swift 的新功能! 今天我们很高兴能给您介绍一下 Swift 5.7 中所有优秀的新功能 我们今天要讲的许多事情都体现了 Swift 的目标 即让 您的开发工作生活更轻松 我们将研究新工具 以帮助您自定义工作流程 并介绍一些令人惊叹的根本性改进 然后我们将讨论 最新的 Swift 并发模型 以及通往 Swift 6 的道路 包括全线程安全 Becca:最后我会介绍 一些使 Swift 更易于读写的语言改进 包括更简洁、更简单的泛型 和强大的新字符串处理工具 Angela:但首先 让我们先谈谈 让 Swift 如此特别的原因之一 那就是所有开发者 Swift 之所以能如此迅速的扩张 要归功于开发者的付出和贡献 社区参与是 Swift 的核心 我们于去年发布了 docC 文档生成工具 并于今年面向社区 提供更多 Swift 计划 并且开源了 Swift.org 网站 要开源运作起来最好 需要有活跃的社区来引领 我们一直在服务器端上的 Swift 和 Swift 多样性项目中 以工作组的方式来管理 并为对特定领域感兴趣的 社区成员提供支持 效果一直很显著 所以我们启动了两个新的工作组 一个用于在 Swift 网站的迭代 并使其更像是一种社区资源 另一个用于 C++ 互用性 以设计改变在 C++ 和 Swift 之间的模型 当我们冒险进入新领域时 我们都需要来自社区内的成员的支持 作为其中的一部分 Swift 多样性工作组 去年推出了 Swift 导师计划 该计划为那些不知道如何开始 或想加深 其在特定领域的专业知识的人 提供了为所有工作组领域 做出贡献的途径 去年的计划取得了巨大的成功 有很多感兴趣的学员慕名而来 因此我们创建了 41 个一对一指导小组 鉴于其取得了巨大成功 此计划将在第二年继续开展 我们希望所有感兴趣的人都能 加入此计划 但要做到这一点 我们需要大家 即正在听课的 优秀且且经验丰富的开发人员 愿意分享广博知识 并建立新联系的人员 因为导师计划不仅仅涉及代码 还涉及在社区内建立关系 一点点指导就可以产生持久的影响 我有真凭实据 去年 Amrit 参加了导师计划 并专注于编译器和语言设计 开始时 Amrit 只是感到好奇 最终却真得做出了贡献 进入一个新领域并不容易 即便如此 她还是找到了成功之路 并感到受到了鼓舞 想要做出更多贡献 像许多其他人一样 这次经历为 Amrit 打开了一扇门 除了编译器和语言设计 去年还有很多重点领域 从技术写作和测试 到为 Swift 软件包做贡献 今年 我们增加了更多领域 新主题将层出不穷 如果您在此列表中 没有看到您感兴趣的内容 您仍然可以在申请表格中提出 另一点是 今年的计划将为初学者 提供全年导师服务 以帮助能力不足 但十分想参与的人 如果您想申请 或者只是想了解更多内容 请查看最新的 Swift 博客文章 在那里 您可以找到重点学员的 详细描述链接 导师计划只是 Swift 多样性 中的其中一项举措 要了解有关导师计划的更多信息 以及关于 Swift 多样性的工作 您可以访问 Swift.org/diversity 为了进一步打开大门 我们想尽量让您 可以轻松地使用 Swift 和现有资源! 我们简化了适用于 Linux 平台的 Swift 工具链分发流程 方式是添加 对 Linux 包格式的支持 现在您可以 使用新原生工具链安装器 直接从 Swift.org 下载 Amazon Linux 2 和 CentOS 7 的 RPM 这些工具链是实验性的 所以请一定在 Swift.org 论坛上分享反馈 Swift 主要用于构建 App 然而 Swift 的愿景 一直是成为可扩展的工具 力求适用于从高级脚本 到裸机环境的所有情况 为了鼓励将 Swift 用于 以前从未使用过的地方 Swift 今年经历了一些重大变化 使标准库更小 以适用于独立的 静态链接的二进制文件 我们放弃了依赖 外部 Unicode 支持库 而是用更快的原生实现来替代 在事件驱动型服务器 解决方案上运行时 更小、更快的二进制文件好处颇多 默认情况下 您会 在 Linux 上获得静态链接 以更好地 支持服务器的容器化部署 这种尺寸减小使 Swift 适合 于受限的环境 这让我们可以在 Apple 的 Secure Enclave Processor 中使用它 Swift 适用于从 App 到服务器 一直到受限处理器的各种场合 将它们联系在一起的是软件包生态系统 今年 Swift 软件包的新功能 会让您的生活更美好 首先 Swift Package Manager 引入了 TOFU 这不是指那个美味的小吃 TOFU 是首字母缩写词 代表首次使用时的信任 这是一种新的安全协议 会在首次下载软件时 记录一个包的指纹 后续下载将验证此指纹 如果指纹不同则报错 这只是内置于软件包生态系统核心的 信任和安全性的一个例子 可帮助您自信地使用它 对于 Swift 开发人员来说 指令插件是改进工作流程的好方法 它们是提供更多可扩展的 且安全的构建工具的第一步 指令插件可用于文档生成 源代码重新格式化等 使用 Swift 您就不必在 Shell 脚本中 编写自动化内容 并维护单独的工作流程 想想开源格式化程序和静态检查工具 现在所有这些开源工具 都可以在 Xcode 和 Swift Package Manager 中找到 指令插件是开源工具和 Swift Package Manager 之间的粘合剂 Swift 项目接纳 开源社区的开发者工具 与您的自动化 工作流程无缝整合 docC 是将文档集成到 您的源代码中的优秀工具 在 Objective-C 和 C 支持的帮助下 今年它更加完善了 让我们看看 使用 docC 创建插件都需要什么 插件只是简单的 Swift 代码 您可以通过创建符合指令插入 协议的结构体来定义插件 然后您只需添加一个函数来告诉 您的插件您要调用哪个工具即可 这个函数就是我们要调用 docC 的地方 一旦您定义了您的插件 它就可以通过 Swift PM 命令行界面 和 Xcode 作为菜单项使用了 现在我们可以告诉 Swift PM 生成文档 它知道要将此操作传递给 docC 可执行文件 还不止于此 还有第二个称为构建工具插件的插件 这些插件是允许您 在构建过程中添加额外的步骤的包 实现构建工具插件时 将为构建系统创建一个指令 以在沙盒中执行 它们不同于指令插件 您可以随时直接执行 并且可以被授予显式权限 以更改包中的文件 构建工具插件可用于源代码生成 或特殊类型文件的自定义处理 在使用构建工具插件时 包布局是这样的 在这个例子中 plugin.Swift 是实施包插件目标的 Swift 脚本 该插件被视为 Swift 可执行文件 并且编写插件的方法与编写 Swift 可执行文件的方法是相同的 要实现插件 您可以定义一组构建指令 它们告诉构建系统 要运行什么可执行指令 以及预期有什么输出 包插件通过您的软件包提供 可扩展的安全解决方案 要详细了解插件的工作原理 以及如何实现自己的插件 请观看“认识 Swift 包插件” 和“创建 Swift 包插件”这两门课程 随着您使用的软件包越来越多 您可能遇到过模块冲突 那是两个单独的包 用同样的名称定义一个模块 为了解决这种问题 Swift 5.7 引入了模块消歧 模块消歧是一种功能 它允许您从 定义模块包的外部重命名模块 在我们的 Stunning App 中 我们引入了 两个都定义了 Logging 模块的包 因此发生了冲突 要为我们的 Stunning App 解决此问题 您只需要在包清单的依赖关系部分 添加 moduleAliases 关键字即可 这样您就可以用两个不同的名字 区分之前拥有 相同名称的模块了 Swift 5.7 带来了 一些惊人的性能改进 让我们先看看构建时间 去年 我们告诉大家我们如何重写了 Swift Driver 它被用于协调 在 Swift 中 Swift 源代码的编译工作 去年的重构 解锁了一些非常重要的变化 让构建速度大幅提升 现在可将驱动程序直接在 Xcode 构建系统中作为框架调用 而不是作为单独的可执行文件 这让它可以更紧密地与构建系统 一起协调构建让诸如并行化之类的 方式成为可能 如果您喜欢听到关于快速构建的内容 您可以在观看 “深入探索 Xcode 构建中的并行” 为了向您展示构建速度有多快 让我们看一些示例 以了解构建我们经常使用的 用 Swift 编写的工具需要多长时间 在 10 核 iMac 上 改进程度 从 5% 一直到 25% 不等 接下来 类型检查的速度也有所改进 今年 我们改进了类型检查器的性能 方式是重新实现泛型系统的关键部分 即根据协议和 where 子句 计算函数签名的部分 在旧的实现中 随着引入越来越多的协议 时间和内存使用会指数级增加 例如在这里 我们有一组复杂的协议 它们定义了一个坐标系 以及许多关联类型的范型要求 以前需要 17 秒来检查此代码 但现在 Swift 5.7 中 此例能够 显着加快类型检查 耗时不足一秒钟 我们还有一些 同样令人印象深刻的运行时间改进 在 Swift 5.7 之前 我们看到 iOS 上的 App 启动时 的协议检查最多需要四秒钟 每次我们启动 App 时 都需要计算协议 这导致添加的协议越多 启动时间更长 现在协议被存入了高速缓冲存储器 根据 App 的编写方式 以及使用了多少协议 这可能意味着 在 iOS 16 上运行时 某些 App 的启动时间减少了一半 “优化 App 大小和运行时性能”课程 将深入探讨如何在您自己的 App 中 利用这些改进 现在是时候讲讲大家 感兴趣的内容了 去年我们推出了新的并发模型 结合 Actor 和 async/await 这对您的 App 并发架构 产生了变革性的影响 async/await 和 Actor 比回调 和手动队列管理更安全、更容易 今年我们进一步充实了模型 将数据竞争安全放在首位 因为并发对于改进 App 代码库 而言是如此重要和不可或缺 我们将这些改变向后部署 到 iOS 13 和 macOS Catalina 为了部署到较旧的操作系统 您的 App 为较久的 OS 捆绑了 Swift 5.5 并发系统运行时间副本 这类似于在 ABI 稳定之前 将 Swift 向后部署到操作系统中 接下来我们将这个模型 推向了新的方向 我们引入了语言功能 和配套软件包 首先让我们谈谈避免数据竞争 在我开始讲解之前 我可能应该退一步 谈谈 Swift 的重要功能之一是 默认的内存安全 Swift 用户不能做 产生无法预测行为的事情 就像在正在修改一个数值时 读取数值 在此例中 我们删除了数组中 与同一数组计数匹配的所有数字 最初 数组的计数是 3 所以我们将从数组中删除 3 但是这样操作之后 计数将变成 2 我们是否从数组中删除 3 和 2 还是只删除 3 呢? 答案是都不是 Swift 会阻止您这样做 因为在修改数组时 访问数组的计数是不安全的 我们的目标是 为线程安全做类似的处理 我们设想一种语言 它可以在默认情况下 消除低级别的数据竞争 换句话说 我们要防止可能会导致 不可预测的行为的并发错误 另一个例子如下 我们使用同样数字的数组 创建了一个后台任务 将 0 附加到数组中 然后我们删除数组的最后一个元素 但是等等 是在附加 0 之前还是之后 删除最后一个元素呢? 答案还是都不是 Swift 会阻止您这样做 因为未通过类似 Actor 等同步访问 在后台任务的修改数组是不安全的 Actor 是消除数据竞争的第一步 今年我们完善了并发模型 离最终目标更进了一步 您可以把每个 Actor 想象成一个独立的小岛 与并发海洋中的其他一切隔离开来 但是当不同的线程想要查询 每个独立 Actor 存储的 信息时会发生什么呢? 我们将在“使用 Swift 并发消除数据竞争” 课程中进一步探讨这一点 从默认内存安全到线程安全 这是 Swift 6 的目标 为此 我们首先用新语言功能 改进了去年的并发模型 我刚才提到了 第二件事我还没有提到 就是新的安全检查可选项 它可识别潜在的数据竞争 您可以通过在构建设置中启用它 尝试更严格的并发检查 我们再来看看 Actor 我们可以采用 这个 Actor 隔离的概念 并进一步推广到分布式 Actor 分布式 Actor 将这些岛屿放在不同的机器上 它们之间有网络相连 这种新语言功能使开发 分布式系统变得更加简单 假设您要创建一个游戏 App 您现在可以 轻松地用 Swift 编写后端 在这里 分布式 Actor 就像是一个 Actor 但它可能在另一台机器上 在此示例中 我们将看到计算机玩家 通过它维护用户玩游戏期间的游戏状态 也可以将 distributed 关键字添加到函数中 我们预计需要为远程机器 上的 Actor 上调用该函数 让我们添加另一个 名为 endOfRound 的函数 它通过遍历玩家对象 在玩家上调用 makeMove 其中一些玩家 可能是本地的或远程的 但我们不必管 是哪个玩家 它与常规 Actor 调用的唯一区别是 因为网络错误 分布式 Actor 调用可能会失败 如果发生网络故障 Actor 方法会抛出一个错误 所以 您需要在 Swift 中添加 所需的 try 关键字 以及常规的 await 关键字 基于这些核心语言原语 我们还构建了一个 开源分布式 Actor 包 它专注于通过 Swift 构建服务器端 集群分布式系统 该软件包包括一个 使用 SwiftNIO 并实现了 SWIM 共识协议的集成网络层 以管理整个集群的状态 “认识 Swift 中的 分布式 Actor” 课程 将详细介绍如何构建 具有这些新功能的分布式系统 我们还推出了一套新的开源算法 为处理 AsyncSequence 时的常见操作 提供简单的开箱即用解决方案 它与 Swift 5.5 一起发布 将这些 API 作为一个软件包发布 为开发人员提供了跨平台 和跨操作系统版本部署的灵活性 有几种方法可以组合多个异步序列 并将值分组到各个集合中 这些只是此包中 包含的一些算法 查看 “认识 Swift 异步算法“ 讲座 了解如何使用这个强大的新 API 但并发系统还包含一个方面 就是性能 今年我们对 Actor 进行了优化 Actor 现在首先执行 优先级最高的工作 并通过与操作系统调度程序 继续进行深度整合 该模型内置了预防优先级反转功能 所以不太重要的工作 不会阻止优先级更高的工作 过去我们很难想象 App 中并发系统对性能的影响 但是现在我们有了一个很棒的新工具 Instruments 中 新的 Swift Concurrency 视图 可以帮助您调查性能问题 Swift Tasks 和 Swift Actors 工具 提供一整套工具来帮助您呈现 并优化并发代码 在顶部 Swift Tasks Instrument 提供实用统计信息 包括同时运行的任务数量 在那个时间点之前创建的任务总数 在此窗口的下半部分 您可以看到所谓的 Task Forest 它提供了图形化表示 展示了结构化并发代码中的 任务之间的父子关系 这只是 Swift Actor Instrument 的详细视图之一 要了解如何使用这个令人兴奋的新工具 请查看 “Swift 并发系统可视化和优化”讲座 并且不要忘记尝试这些新软件包 请大胆地在论坛上告诉我们使用情况 现在我们请 Becca 来谈谈 对 Swift 语言可用性的诸多改进 Becca:语言是工具 关于工具有一些有意思的事情 工具真得可以影响您建造的东西 当您只有一把锤子时 您会用钉子而不是螺丝来建造东西 即使您有全套工具 如果您的锤子 有一个又大又防滑的把手 而您的螺丝刀是塑料的 很难握住 您可能仍然倾向于用钉子建造 语言也是如此 如果 Swift 有一个工具 能很好地表达事物 人们就会更频繁地使用它 Swift 用于表达您希望代码做什么 的工具在许多方面都有所改进 其中一些更改是 让您常做的事更加便利 例如 在等号两边添加相同名称的 if let 是非常常见的 毕竟 对于展开的值 可能没有比给可选值取的名字 更好的名字了 但是当名字真的很长的时候 这种重复就会变得麻烦 您可能会想缩写这个名字 但是随后您的代码变得模棱两可 如果您稍后重命名可选变量 缩写可能不会被同步 Swift 5.7 为这种模式引入了一种 全新的简便方法 如果您展开一个可选值 并希望展开的值具有相同的名称 只需删除右边的代码即可 Swift 会假设名称是一样的 当然 这种方式同样适用于 guard 甚至 while 我们还查看了当您做了细微更改 某个功能突然停止工作的地方 例如 Swift 总是能够根据 单语句闭包内编写的代码推导出 一个调用将返回什么类型 在这个 compactMap 调用中 闭包返回 parseLine 并且 parseLine 函数 返回了一个 MailmapEntry Swift 可以推导出 entries 应该是 一个 MailmapEntry 的数组 这现在适用于具有多个语句 或控制流功能的更复杂的闭包 所以您可以使用 do-catch 或 if…else 或只是添加一个 print 调用 而无需手动指定闭包的结果类型 我们关注的另一件事是那些并未 真正标示任何实际危险的危险标志 Swift 非常注重类型和内存安全 为了不让您犯错 它绝对不会在不同指针类型的指针之间自动转换 也不会在原始指针和 类型化指针之间自动转换 这与 C 非常不同 C 允许某些转换 例如 您可以更改带符号类型的指针 或将任何指针转换为 char* 以将其作为字节访问 这不违反任何 C 指针规则 但有时这些指针行为的差异会 在 C API 导入 Swift 时会出现问题 最初的开发者在设计 API 时可能有轻微的不匹配 这些不匹配在 C 语言中 可以通过自动转换来处理 但在 Swift 中却是错误 在 Swift 中 将一种类型的指针 当作不同类型来访问是非常危险的 所以您必须明确地描述您在做什么 但如果我们向 C 传递指针 这一切都毫无意义 因为在 C 中 指针不匹配是完全合法的 所以在这种情况下 我们把一件非常简单的事情 当作危险的事情来对待 这很重要 因为 Swift 既重视类型安全 它还重视对 C 系列代码的轻松访问 这就是为什么 C 和 Objective-C 的互操作 如此丰富和无缝 为什么 Swift 项目 组建了 Angela 在前面提到的 C++ 工作组 开始构建为 C++ 提供同样强大的互用性 我们不想让使用这样的 C 函数 变得非常痛苦 这种痛苦是可以避免的 所以 Swift 制定了一套单独的规则 用于调用导入的函数和方法 它允许在 C 中合法的指针转换 即使它们通常在 Swift 中是不合法的 这样您的 Swift 代码 就可以无缝地使用这些 API 到目前为止 我们已经讨论了对 您已经拥有的工具的一些小的改进 今年 Swift 中还新增了一个全新工具 用于从字符串中提取信息 这里有一个函数 可以从一个字符串中 解析出一些信息 这种任务对于 Swift 来说 一直有点难 您得反复搜索、分割和切片 直到您得到您想要的 当人们注意到这一点时 他们往往会关注一些小事 比如操纵字符串索引有多冗长 但我认为这有点忽略了大局 因为即使我们改变了这个语法 它也不能帮助您 回答您提出的基本问题 当您查看此代码时 传入它的行变量究竟是什么样子的? 它想拆开什么样的字符串? 如果您凝视它足够长的时间 您可能会意识到 它正在解析 mailmap 的简化版本 即您放入 git 存储库的 以更正之前提交的 开发人员姓名的文件 但是通过搜索和切片来提取这些信息 太复杂了 很难弄清楚 您执着于如何分割字符串 以至于忘记了那个字符串是什么 问题不在于这两种表达方式 问题是整个事情 我们需要打破这一切 并用更好的东西代替它 我们需要一种不同的方法 在这种方法中 您的代码可以描述出 您想要匹配的字符串 语言会说明如何去做 是一种声明性方法 而不是强制性方法 在 Swift 5.7 中 您现在可以 通过编写正则表达式来做到这一点 正则表达式是 一种描述字符串中模式的方法 50 多年来 语言和工具 允许开发人员编以紧凑 且信息丰富的语法写正则表达式 开发者中的一些人 已在 Xcode 查找栏中使用过它们 在 grep 等指令行工具中 在 Foundation 的 NSRegularExpression 类 或其他编程语言中 Swift 的正则表达式字面量 现在支持该语法 它的工作原理 就像在任何其他开发工具中一样 但是开发者中的一些人以前没用过正则表达式 您可能会问:“那是真正的代码吗? 还是一只猫从键盘上乱踩出来的?” 我不会责备您 正则表达式字面量 是用符号和助记符编写的 您必须记住才能阅读它们 对于懂这种语言的人来说 即使是 这个正则表达式中最粗糙的部分 比如和开发者名字匹配的部分 只是几个简单匹配规则的组合 但要把很多行为 塞进 11 个字符里是很难的 正则表达式字面量非常紧凑 即使是经验丰富的开发人员 有时需要一些时间 来理解一个复杂的表达式 但是如果您能写出 同样的匹配规则 只是用词汇而不是符号? 这似乎更容易理解 其实放在一起 您会得到一些看起来 很像 SwiftUI 的东西 这将是很好的 替代正则表达式的文字 不是吗? 所以 Swift 支持它是一件好事! RegexBuilder 库提供了一个全新的 SwiftUI 风格的正则表达式语言 更易于使用 并且比传统语法更具可读性 它可以做 与正则表达式文字相同的事情 但它用您能理解或查阅的语言 描述了它的行为 而不是用您必须记住的符号和缩写 正则表达式构建器非常适合初学者 但它的功能不仅仅适合初学者 它具有强大的功能 远远超过了正则表达式字面量 首先您可以把正则表达式 变成一个可重用的正则表达式组件 就像您可以把 SwiftUI 视图层次 变成一个视图一样 您可以使用构建器语法创建的 其他正则表达式中的这些组件 您甚至可以让它们递归 正则表达式构建器还支持将 一些 Swift 类型 直接放入正则表达式中 例如 字符串文字只匹配 里面的确切文本 不需要特殊的转义 您还可以使用在正则表达式生成器 中的正则表达式字面量 所以您可以 在正则表达式构建器清晰度 和正则表达式字面量的 简洁性之间取得平衡 其他类型 比如这个 Foundation 日期格式风格 可以将自定义解析逻辑 与正则表达式构建器集成 甚至在捕获数据之前 将其转换为更丰富的类型 最后 无论您使用哪种语法 正则表达式支持很多有用的匹配方法 以及易于使用的强类型捕获 对于那些惴惴不安的 痴迷于正则表达式的人来说 Swift Regex 使用全新的开源匹配引擎 具有可与最先进的正则表达式实现 相匹配的功能集 字面量语法与 Unicode 正则表达式标准兼容 它具有不同寻常的 Unicode 正确性级别 例如 点默认匹配整个字符 不是 Unicode.Scalar 或 UTF-8 字节 要使用 Swift Regex 您的 App 需要在 在内置 Swift Regex 引擎的 OS 上运行 比如 macOS 13 或 iOS 16 Swift Regex 是一门完整的语言 嗯 真的是两种语言 所以还有很多内容要讲 “Swift 正则表达式简介” “深入了解 Swift 正则表达式” 将为您提供 更多关于其用途的详细信息 最后 我们在一个地方 对现有的工具进行了全面的检查 并进行了大量的修改来改进它们 那就是在泛型和协议中 为了向您展示这些工具是如何改进的 我需要举一个协议示例 假设您正在 编写一个 git 客户端 而且您必须 以两种不同的方式表示邮件映射 当您显示提交时 您使用字典类型 快速查找名称 但是当您让用户编辑邮件映射时 您使用数组类型 按照其原来的顺序来保留条目 您有一个叫做 Mailmap 的协议 它们都符合这个协议 因此您的邮件映射解析器 可将条目添加到任一类型 但是解析器可以通过两种方式 使用 Mailmap 协议 我写了两个不同的版本 addEntries 函数来加以说明 但实际上有点难解释 它们的不同之处 因为 Swift 对两种不同的事物 使用相同的语法 看起来 “Mailmap” 这个词 在这里意味着一件事 但它在这里的意思略有不同
当您命名协议是用在在继承列表 泛型参数列表 泛型一致性约束 或不透明的结果类型中时 它的意思是一个符合这个协议的实例 但在变量类型 泛型变元 泛型同类型约束 或函数参数或结果类型中 它实际上的意思是 “一个包含符合此协议的实例的容器” 这种区别很重要 因为容器 通常使用更多空间 需要更多时间来操作 并不具备里面的 实例的所有能力 但是您使用容器的地方 和不使用容器的地方看起来很像 所以很难弄清楚您是否正在使用容器 Swift 5.7 修复了这个疏忽 当您使用包含一个 符合类型的容器之一时 Swift 希望您能写入 any 关键字 这在 Swift 5.7 之前 有效的代码中不是强制性的 但我们鼓励您这样做 您会在生成的界面 和错误消息中看到它 即使您没有明确写出来 所以在右侧的列中写入所有 这些内容的首选方式 是带有 any 关键字 这样您就能知道 您是不是在使用其中一个容器 在此例中 any 关键字标记了参数之一 更容易解释 这两个函数的区别 addEntries1 将 Mailmap 作为泛型类型 addEntries2 将其视为 any 类型 并且当您遇到 any 类型的限制之一时 错误消息也更容易 解释发生了什么 例如这个 mergeMailmaps 函数视图将 一个 any Mailmap 传递 给泛型 Mailmap 参数 这曾经产生错误 说 Mailmap 与它自己不符 这似乎有点自相矛盾 但是现在我们有了 any 类型的概念 我们就可以 更清楚地解释正在发生的事情 问题是 any Mailmap 是包含 Maimap 的容器 不符合 Mailmap 协议 但容器是您试图传递的东西 并且它不适合泛型参数 如果要在此处传递容器内的实例 您必须以某种方式打开容器 取出里面的 Mailmap 然后传递它 但实际上 在这种简单的情况下 Swift 会为您做这件事 打开容器 取出里面的实例 并将其传递给泛型参数 所以您不会再经常 看到这个错误信息了 还有一个更令人兴奋的 对 any 类型的改进 以前协议不能用作 any 类型 如果它使用 Self 类型或具有关联类型 甚至只是符合一个协议 比如 Equatable 但在 Swift 5.7 中 这个错误 消失了 很多开发者都在为此苦苦挣扎 所以我们很高兴能从源头上修复它 现在对于像 Mailmap 这样的协议来说 这足够令人兴奋 但还不止于此 因为即使是 非常复杂的协议 比如 Collection 也可以用作 any 类型 您甚至可以指定元素类型 这要归功于 一个名为“主关联类型”的新功能 很多关联类型 基本上只是实现细节 您通常不会关心集合为其索引 迭代器或子序列使用哪种类型 您只需要使用它支持的类型 但它的 Element 就不同了 您可能并不一定关心 集合究竟使用哪种元素类型 但您可能要用元素做点什么 所以您需要约束它们或返回它们等等 当您有像 Element 这样 几乎每个协议的用户 都会关心的关联类型时 您可以将其名称放在协议名称之后 用尖括号括上 以使其成为主关联类型 这样您就可以用尖括号语法 来限制协议的的主关联类型 在可以在写协议名称的 任何地方都添加尖括号 包括在任何 Collection 中 现在 开发者中的一些人 可能正在看这种类型 并想“等一下 是不是已经有一个 叫做 AnyCollection 的东西 一起运行并且 Any 是大写的了?” 您是对的 是有的! 旧的 AnyCollection 是一个去类型包装器 它是一个作用 与 any 类型相同的 手写的结构 不同之处在于 AnyCollection 结构体 只是一行又一行最无聊的模板代码 您在生活中见过的 而 any 类型是一种内置的语言功能 基本上做同样的事情 是免费的! 现在 为了向后兼容 AnyCollection 结构体将继续存在 因为它有 一些 any 类型无法完全匹配的功能 但如果您的代码中 有自己的去类型包装器 您可能想看看 是否可以使用内置的 any 类型 而不是通过容器类或闭包重新实现它们 或甚至只是用类型别名替换它们 所以 Swift 极大地 改进了 any 类型 它引入了 any 关键字 这样您就可以看到您在哪里使用它们 它允许您将它们传递给泛型变元 它取消了使许多协议不能 与之一起使用的限制 它甚至可以让您约束 any 类型的主关联类型 但即使有了所有这些改进 any 类型仍然有限制 例如 即使您现在 可以使用 any Mailmap 当 Mailmap 符合 Equatable 时 您仍然不能对它们使用等于运算符 因为等于运算符需要两个 Mailmap 具有相同的具体类型 但当您使用两个 any Mailmap 时 并不能保证这一点 所以即使 Swift 对 any 类型做了很多改进 它们在能力和性能方面 仍然有不可忽视的限制 这就是为什么很多时候 您不应该使用它们 而应该改用泛型 那么让我们回到 addEntries 的两个版本 并运用这种智慧 两个版本的功能完全相同 但最上面的那个使用泛型类型 底部的使用 any 类型 泛型版本可能会更有效 本领更大 所以您应该使用泛型版本 但您可能很想使用 any 类型 因为它们更容易读写 要编写泛型版本 您需要声明两个泛型类型名称 约束它们 最后使用这些泛型类型名称 作为参数类型 与写 “any Collection” 和 “any Mailmap” 相比 这简直令人筋疲力尽 因此尽管有缺点 您还是很想使用 any 类型 但这和我之前说的一样 用锤子代替螺丝刀 因为锤子有一个又大又防滑的把手 您不应该做出这样的选择 所以 Swift 让泛型变得更容易使用 和 any 类型一样 如果泛型参数只用在一个地方 您现在可以 用关键字 some 作为速记写法 它甚至支持主要的关联类型 所以您可以用更容易理解的代码 接受所有邮件映射条目集合 在您的工具箱里 没有理由再避免使用泛型了 如果您可以在泛型和 any 类型之间进行选择 泛型将同样易于使用 只写 “some” 而不是 “any” 即可 所以您不妨使用 最好的工具来完成这项工作 我只是讲解了协议和泛型 变更的一些皮毛 为了深入了解 并介绍所有 Swift 泛型功能 今年我们还有两场讲座 即“拥抱 Swift 泛型” 和“在 Swift 中设计协议接口”
现在 Angela 和我差不多介绍了 二十多个 Swift 更改 我们在这次课程中 还有很多没讲到的内容 这些变化中的每一个都 经过了确定标准、提议、审查 并在 Swift 论坛的 Evolution 板上被公开接受 所有这些都是 在 Apple 以外的社区成员 的帮助下成形和得以实现的 如果您是这些人中的一员 感谢各位开发者 让 Swift 5.7 成为一个伟大的版本 如果您想帮助决定接下来的事情 访问 Swift.org/contributing 以了解如何参与 感谢您付出的时间 Angela:编码快乐
♪
-
-
7:19 - Command plugins
@main struct MyPlugin: CommandPlugin { func performCommand(context: PluginContext, arguments: [String]) throws { let process = try Process.run(doccExec, arguments: doccArgs) process.waitUntilExit() } }
-
8:34 - Build tool plugins
import PackagePlugin @main struct MyCoolPlugin: BuildToolPlugin { func createBuildCommands(context: TargetBuildContext) throws -> [Command] { // Run some command } }
-
8:39 - Implementing a build tool plugin
import PackagePlugin @main struct MyCoolPlugin: BuildToolPlugin { func createBuildCommands(context: TargetBuildContext) throws -> [Command] { let generatedSources = context.pluginWorkDirectory.appending("GeneratedSources") return [ .buildCommand( displayName: "Running MyTool", executable: try context.tool(named: "mycooltool").path, arguments: ["create"], outputFilesDirectory: generatedSources) ] } }
-
9:23 - Module disambiguation with module aliases
let package = Package( name: "MyStunningApp", dependencies: [ .package(url: "https://.../swift-metrics.git"), .package(url: "https://.../swift-log.git") ], products: [ .executable(name: "MyStunningApp", targets: ["MyStunningApp"]) ], targets: [ .executableTarget( name: "MyStunningApp", dependencies: [ .product(name: "Logging", package: "swift-log"), .product(name: "Metrics", package: "swift-metrics", moduleAliases: ["Logging": "MetricsLogging"]), ])])
-
9:42 - Distinguishing between modules with the same name
// MyStunningApp import Logging // from swift-log import MetricsLogging // from swift-metrics let swiftLogger = Logging.Logger() let metricsLogger = MetricsLogging.Logger()
-
11:09 - Example set of protocols
public protocol NonEmptyProtocol: Collection where Element == C.Element, Index == C.Index { associatedtype C: Collection } public protocol MultiPoint { associatedtype C: CoordinateSystem typealias P = Self.C.P associatedtype X: NonEmptyProtocol where X.C: NonEmptyProtocol, X.Element == Self.P } public protocol CoordinateSystem { associatedtype P: Point where Self.P.C == Self associatedtype S: Size where Self.S.C == Self associatedtype L: Line where Self.L.C == Self associatedtype B: BoundingBox where Self.B.C == Self } public protocol Line: MultiPoint {} public protocol Size { associatedtype C: CoordinateSystem where Self.C.S == Self } public protocol BoundingBox { associatedtype C: CoordinateSystem typealias P = Self.C.P typealias S = Self.C.S } public protocol Point { associatedtype C: CoordinateSystem where Self.C.P == Self }
-
13:14 - Memory safety in Swift
var numbers = [3, 2, 1] numbers.removeAll(where: { number in number == numbers.count })
-
14:10 - Thread safety in Swift
var numbers = [3, 2, 1] Task { numbers.append(0) } numbers.removeLast()
-
15:54 - A distributed actor player and a distributed function
distributed actor Player { var ai: PlayerBotAI? var gameState: GameState distributed func makeMove() -> GameMove { return ai.decideNextMove(given: &gameState) } }
-
16:20 - A distributed actor call
func endOfRound(players: [Player]) async throws { // Have each of the players make their move for player in players { let move = try await player.makeMove() } }
-
20:12 - Optional unwrapping
if let mailmapURL = mailmapURL { mailmapLines = try String(contentsOf: mailmapURL).split(separator: "\n") }
-
20:29 - Optional unwrapping with long variable names
if let workingDirectoryMailmapURL = workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n") }
-
20:35 - Cryptic abbreviated variable names
if let wdmu = workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: wdmu).split(separator: "\n") }
-
20:46 - Unwrapping optionals in Swift 5.7
if let workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n") } guard let workingDirectoryMailmapURL else { return } mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
-
21:07 - Closure type inference
let entries = mailmapLines.compactMap { line in try? parseLine(line) } func parseLine(_ line: Substring) throws -> MailmapEntry { … }
-
21:33 - Type inference for complicated closures
let entries = mailmapLines.compactMap { line in do { return try parseLine(line) } catch { logger.warn("Mailmap error: \(error)") return nil } } func parseLine(_ line: Substring) throws -> MailmapEntry { … }
-
22:15 - Mismatches that are harmless in C...
// Mismatches that are harmless in C… int mailmap_get_size(mailmap_t *map); void mailmap_truncate(mailmap_t *map, unsigned *sizeInOut); void remove_duplicates(mailmap_t *map) { int size = mailmap_get_size(map); size -= move_duplicates_to_end(map); mailmap_truncate(map, &size); } // …cause problems in Swift. func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) { var size = mailmap_get_size(map) size -= moveDuplicatesToEnd(map) mailmap_truncate(map, &size) }
-
22:33 - Better interoperability with C-family code
func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) { var size = mailmap_get_size(map) size -= moveDuplicatesToEnd(map) withUnsafeMutablePointer(to: &size) { signedSizePtr in signedSizePtr.withMemoryRebound(to: UInt32.self, capacity: 1) { unsignedSizePtr in mailmap_truncate(map, unsignedSizePtr) } } }
-
23:41 - String parsing is hard
func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:05 - String parsing is still hard with better indexing
func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[(emailEnd + 1)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[(nameEnd + 1)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:20 - What's the problem?
let line = "Becca Royal-Gordon <beccarg@apple.com> # Comment" func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:55 - Drawing a picture
"Becca Royal-Gordon <beccarg@apple.com> # Comment" / space name space < email > space # or EOL / / \h* ( [^<#]+? )?? \h* < ( [^>#]+ ) > \h* (?: #|\Z) /
-
25:10 - Swift Regex using a literal
func parseLine(_ line: Substring) throws -> MailmapEntry { let regex = /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/ guard let match = line.prefixMatch(of: regex) else { throw MailmapError.badLine } return MailmapEntry(name: match.1, email: match.2) }
-
25:46 - Did a cat walk across your keyboard?
/\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/
-
26:34 - Regex builder
import RegexBuilder let regex = Regex { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) ChoiceOf { "#" Anchor.endOfSubjectBeforeNewline } }
-
27:05 - Turn a regex into a reusable component
struct MailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) ChoiceOf { "#" Anchor.endOfSubjectBeforeNewline } } }
-
27:30 - Use regex literals within a builder
struct MailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) /#|\Z/ } }
-
27:39 - Use Date parsers within Regex builders
struct DatedMailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring, Date)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) Capture(.iso8601.year().month().day()) ZeroOrMore(.horizontalWhitespace) /#|\Z/ } }
-
27:49 - Matching methods and strongly type captures in Regex
func parseLine(_ line: Substring) throws -> MailmapEntry { let regex = /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/ // or let regex = MailmapLine() guard let match = line.prefixMatch(of: regex) else { throw MailmapError.badLine } return MailmapEntry(name: match.1, email: match.2) }
-
29:02 - A use case for protocols
/// Used in the commit list UI struct HashedMailmap { var replacementNames: [String: String] = [:] } /// Used in the mailmap editor UI struct OrderedMailmap { var entries: [MailmapEntry] = [] } protocol Mailmap { mutating func addEntry(_ entry: MailmapEntry) } extension HashedMailmap: Mailmap { … } extension OrderedMailmap: Mailmap { … }
-
29:26 - Using the Mailmap protocol
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
31:05 - `Mailmap` and `any Mailmap`
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
31:17 - Improvements to `any` types
extension Mailmap { mutating func mergeEntries<Other: Mailmap>(from other: Other) { … } } func mergeMailmaps(_ a: any Mailmap, _ b: any Mailmap) -> any Mailmap { var copy = a copy.mergeEntries(from: b) return a }
-
32:21 - More improvements to `any` types
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
32:54 - Using Collection as an `any` type
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
33:04 - Primary associated types
protocol Collection<Element>: Sequence { associatedtype Index: Comparable associatedtype Iterator: IteratorProtocol<Element> associatedtype SubSequence: Collection<Element> where SubSequence.Index == Index, SubSequence.SubSequence == SubSequence associatedtype Element }
-
33:42 - Using primary associated types in Collection
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } } extension Collection<MailmapEntry> { … }
-
34:35 - Example of type erasing wrappers
struct AnySprocket: Sprocket { private class Base { … } private class Box<S: Sprocket>: Base { … } private var box: Base // …dozens of lines of code you hate // having to maintain… }
-
34:38 - Replace boxes with built-in `any` types
struct AnySprocket: Sprocket { private var box: any Sprocket // …fewer lines of code you hate // having to maintain… }
-
34:44 - Or try type aliases
typealias AnySprocket = any Sprocket
-
35:09 - `any` types have important limitations
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func areMailmapsIdentical(_ a: any Mailmap, _ b: any Mailmap) -> Bool { return a == b }
-
35:44 - Using generic types vs. `any` types
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
36:40 - `some Mailmap` and `any Mailmap`
func addEntries1<Entries: Collection<MailmapEntry>>(_ entries: Entries, to mailmap: inout some Mailmap) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
36:50 - `some Mailmap` and `any Mailmap` with Collection and primary associated types
func addEntries1(_ entries: some Collection<MailmapEntry>, to mailmap: inout some Mailmap) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。