大多数浏览器和
Developer App 均支持流媒体播放。
-
Swift 概览:探索 Swift 的功能与设计
了解 Swift 编程语言的基本功能和设计理念。我们将探索如何进行数据建模、处理错误、使用协议、编写并发代码以及更多操作,同时为你讲解如何构建包含资源库、HTTP 服务器和命令行客户端的 Swift 软件包。无论你是 Swift 开发新手,还是从一开始就选择 Swift 的资深用户,本讲座都将帮你充分利用这一编程语言。
章节
- 0:00 - Introduction
- 0:51 - Agenda
- 1:05 - The example
- 1:32 - Value types
- 4:26 - Errors and optionals
- 9:47 - Code organization
- 11:58 - Classes
- 14:06 - Protocols
- 18:33 - Concurrency
- 23:13 - Extensibility
- 26:55 - Wrap up
资源
- Forum: Programming Languages
- The Swift Programming Language
- Tools used: Ubuntu
- Tools used: Visual Studio Code
- Tools used: Windows
- Value and Reference types
- Wrapping C/C++ Library in Swift
相关视频
WWDC23
WWDC22
WWDC21
-
下载
大家好 我叫 Allan Shortlidge 从事 Swift 编译器的开发工作 今天很高兴能为大家介绍 我最喜欢的编程语言 Swift
Swift 是一种现代编程语言 它功能丰富 性能卓越 安全性也毫不逊色 它的轻量级语法 让编程变得轻松愉快 强大的功能 则有助于你大大提升工作效率 Swift 是编写在 Apple 设备上 运行的 App 的首选语言 并因此而得名 但它的用途远不止这一点 作为一种跨平台的系统语言 Swift 也非常适合编写 服务器端应用程序 随着去年开展 Embedded Swift 项目 Swift 可以缩小规模 以适应资源最受限的环境 比如驱动智能家居设备的芯片 今天 我们将了解一下 Swift 的核心功能 我们不会全面介绍 Swift 的各个 方面 也不会深入探讨任何一个话题 我们的目标是熟悉这个语言 并了解 Swift 的独特设计原则 在介绍 Swift 的同时 我将通过为下一个出色的 社交网络构建基础架构 来演示它的功能 代码将组织成一个 Swift 软件包 其中包含三个组件 第一个组件是库 用于提供在图谱中 表示用户的数据模型 第二个组件是 HTTP 服务器 可以响应图谱查询 最后 我将介绍 一款命令行实用程序 可用于向我们的服务器发送请求 在这场 Swift 之旅的开始 我们先来了解一个基本的编程概念 表示数据 在 Swift 中 表示数据的 主要方式是利用值类型 我将借助一些代码来说明这一点 在 Swift 中 我们可以使用 var 关键字引入变量 就像这样
我们来声明第二个变量 y 并使用 x 的值为它赋值 如果我更改 x 的值 会发生什么 在输出结果中 y 的值仍然为 1 你可能对这一点并不感到惊讶 这就是整数类型 在大多数语言中的运作方式 不过 它说明了当我们说某一类型 具有值语义时 我们指的是什么 值类型有几个重要的属性 值类型的实例不会共享状态 因此更改某个值 不会影响同一类型的其他值 它们也没有身份标识 这意味着 如果两个值相同 这两个值就可以互换 在大多数语言中 整数、布尔值和浮点数等基本类型 都属于值类型 但是在 Swift 中 值类型无处不在 另一种使代码更易于理解的方法是 控制数据的可变性 我使用 var 关键字 引入了 x 和 y 这使它们具有可变性 但是如果我使用 let 而不是 var Swift 可确保这个值不会发生变化
让我们通过在图谱中引入 一个 Users 模型来 构建一个更复杂的数据类型 Swift 中包含将多个值 聚合成一个值的结构体 我将创建一个结构体 并命名为 User
这个结构体需要一些属性 一个用于用户名 另一个用于控制用户是否可见 最后是好友列表 我使用用户名字符串数组来表示它 我将创建一个名为 Alice 的用户 并为她添加一些好友
请注意 我没有 为 Alice 声明类型 Swift 是一种类型安全的语言 每个变量在编译时都包含一个类型 但在这里 类型可以推断为 User 因此我不必写出 接下来 我将创建名为 Bruno 的用户 并为他添加 Alice 的好友 如果我再为 Alice 添加一名好友 现在会发生什么情况
输出结果显示 Alice 和 Bruno 有不同的好友 这是因为数组 在 Swift 中是值类型 当我将 Alice 的好友赋值到 Bruno 的好友时 数组被拷贝 由于 User 结构体由值类型组成 它本身也自动成为了一个值类型 你在典型的 Swift 代码中 遇到的大多数类型都是值类型 类等引用类型也确实存在 我们稍后将为大家介绍 但它们的用途更加特定 Swift 强调值类型和不可变性 因为控制值何时可以改变 使得代码更易于理解 尤其是在并发编程等棘手领域 接下来 我们来谈谈错误 错误是日常编程中 不可避免的一部分 例如磁盘空间已满 网络连接失败 用户提供错误数据 不过 你的程序需要继续运行 同时在出现错误时通知用户 Swift 提供了一种错误处理模型 可以轻松报告 并妥善处理错误 Swift 的错误处理理念 可归纳为三点 首先 应标记出代码中 可能导致错误的部分 这样你就不会感到意外 其次 错误应包含充分的背景信息 以便采取相关措施 最后 Swift 会区分 可恢复错误和 程序员错误 当网络连接失败时 程序应继续运行 另一方面 越界数组访问 可能表明代码是错误的 Swift 会停止程序以防止错误 升级为安全问题
我们来看看如何通过检查错误条件 来提高 User 模型的安全性 friends 数组目前可以 直接进行修改 这可能会导致无效状态 例如用户与自己成为好友 或好友列表包含重复值 我将创建一个名为 addFriend 的 方法 以便验证数组的新增内容
默认情况下 结构体中的方法 不能修改结构体的属性 需要将这个方法声明为 mutating 以便可以更改 friends 现在我想避免 直接使用 friends 属性 因此为它设置一个 private setter 并改为调用 addFriend
接下来我想报告 addFriend 中的错误 这意味着我需要用一种方法 来表示这些错误 Swift 枚举是一种很好的错误类型
枚举代表不同用例之间的一种选择 可以枚举出所有可能的错误原因 我只需要让枚举 符合 Error 协议即可 稍后我们将更详细地介绍协议 接下来 我们可以检查 addFriend 的输入是否无效 如果无效 则抛出错误 编译器提示我们 现在需要将这个方法 标记为“throws” 因为它是错误源
我本可以利用 if 语句 来检查输入 但 Swift 的 guard 语句 非常适合用来检测错误条件 因为它们要求我们 在不满足 guard 条件的情况下 从函数中返回 由于 addFriend 现在会抛出错误 诊断结果表明我们 有一个尚未处理的 错误源 我们需要用 try 关键字 标记调用 以表明可能会发生错误 我想看看错误情形 因此我将触发一个错误 并通过 用 do/catch 块封装调用来进行观察
太棒了 系统抛出了 duplicateFriend 错误 不过 这个错误也存在一个问题 它并没有指明哪个好友是重复的 我可以通过为这个用例添加关联值 来提供更多上下文
现在 用户名和 错误信息一起显示出来 接下来 让我们编写一个原型查询 用于根据用户名查找用户 我需要一个存储用户的位置 因此我创建了一个字典 可将用户名映射到用户结构体 我们的查询函数需要使用一种方法 来处理找不到用户的情况 它可以抛出一个错误 但也可以选择 将返回值设为可选类型 Swift 针对可选值提供了 丰富的内置支持 可选值可以是 nil 也可以是可选项所封装的 任意类型的有效值 要获取所存储的可选值 必须对它进行解包 Swift 要求代码同时处理 nil 值和非 nil 值 从而可以避免 其他语言中的常见错误 即防止意外的 nil 值导致程序崩溃 我们来看看 如何在代码中使用可选值 在 findUser 函数中 我可以直接返回对字典的查询结果 这里出现一个错误 因为字典的下标运算符 返回一个可选的 User 这是合理的 因为可能 不存在与这个键对应的值 我将添加一个问号 将函数返回类型 更新为可选类型 我们来尝试调用 findUser
由于返回的是可选类型 我们需要对结果进行解包 才能处理 User Swift 中对可选类型 进行解包的最常用方法之一 是使用 if let 语法 如果存在非 nil 值 它将被绑定到 这个 if 语句主体中的 let 语句 在我完全确定运行时 总会有一个有效值的情况下 我还可以使用感叹号 强制对可选类型进行解包
Swift 将在运行时 检查我的假设是否正确
糟糕 字典中不存在名为 dash 的用户 因此程序停止了 下次我会更加仔细一点 Swift 中的错误处理和可选 类型解包有一些共同之处 两者都旨在确保代码结构能够 处理各种可能情况 程序中可能遇到错误的所有地方 都必须捕获或传播错误 而 throws 和 try 关键字 会准确显示错误发生的位置 在处理可选值时 你必须在使用前通过解包 来验证这个值是否存在 错误和可选类型的设计 让你能够更轻松地在 Swift 中 编写正确且可调式的程序 我已经概述了 社交图谱的基本数据模型 我想现在该开始 为代码添加一些结构体了 Swift 支持的两个代码组织单元 是模块和软件包 Swift 中的模块 由一系列源文件组成 这些文件总是一起构建 模块还可以依赖于其他模块 例如 代表 App 的模块 可能依赖于提供 App 和服务器 所需的核心功能的库模块 模块集合可以作为软件包分发 最后 一个软件包中的模块 还可以依赖于 另一个软件包中的模块 Swift Package Manager 是用于管理软件包的工具 你可以从命令行调用这款工具 来构建、测试和运行代码 你还可以使用 Xcode 或 VS Code 来处理 Swift 软件包 如果你正在寻找一个库 来完成特定任务 比如创建 HTTP 服务器 你也许可以在 Swift Package Index 中找到一个 开源 Swift 软件包来完成这项任务 稍后我将使用一些开源软件包 来构建我的服务器 和命令行实用程序 回到代码 我已将到目前为止所编写的内容 重新组织成一个软件包 软件包中的第一个模块是库模块 其中包含社交图谱数据模型 这个库还附有一些测试 我们之前定义的 错误枚举和 User 结构体 现在都位于各自的文件中 我们来检查一下 User 结构体 你会注意到我对它进行了一些调整 它和它的许多成员 现在都有了 public 修饰符 这样就可以 在库外的代码中使用它们 public 是 Swift 中 几种不同的访问控制级别之一 此外还包括 private、 internal 和 package 级别 标记为 private 的声明 只能由同一文件中的代码访问 internal 声明 只能由同一模块中的 其他代码访问 如果未指定访问级别 Swift 会隐式使用 internal 级别 package 声明可以从同一软件包 中的其他模块进行访问 public 声明可以 从任何其他模块进行访问 到目前为止 我只介绍了值类型 但有时你需要表示共享的可变状态 为此 Swift 提供了引用类型 比如类 稍后 我将构建一个 HTTP 服务器 用于储存社交图谱数据模型 它需要响应一些操作请求 比如添加好友或列出好友 当收到请求时 我希望用于处理请求的代码 使用抽象方法来访问用户集合 这个集合需要 由多个请求共享和更改 这意味着我应该使用 引用类型对它进行封装 我们通过一个简单的例子 来深入了解一下类 如果你有使用面向对象 的语言进行编程的经验 那么你对 Swift 的类 应该不会感到陌生 类支持单一继承 比如在这个例子中 Cat 类继承了超类 Pet 子类的方法可以覆盖超类的方法 从子类到超类 以及从超类到子类的类型转换 都会按你预期的那样进行 Swift 会自动管理内存 对于引用类型 Swift 有一项名为 “自动引用计数”的功能 编译器会在后台确保 只要存在对某一对象的引用 这个对象就会保持活跃状态 编译器会通过提高和降低 引用计数来实现这一点 当不存在其他引用时 对象就会立即停止分配 自动引用计数是可预测的 这对于提升性能非常有帮助 不过 挑战之一是 可能会形成循环引用 从而导致对象无法释放 这里有一个包含宠物数组的 Owner 类 Pet 类中有一个指向 Owner 的引用 这就创建了一个循环引用 为了打破这种循环 我可以使用弱引用 来避免增加 Owner 的引用计数 请注意 当我们将 owner 属性设置为 弱引用时 它也会变成可选类型 如果 Owner 实例 在 Pet 实例之前释放 这个属性将变为 nil 前面我说过 Swift 强调值类型 但类也扮演着重要的角色 如果你需要共享的可变状态、 具有标识的对象或继承 那么类就是完成 这项工作的理想工具 在许多面向对象的语言中 继承是多态性的主要机制 不过在 Swift 中 协议提供了 一种更通用的方式来构建抽象 而且它们对值类型和引用类型 都同样有效 协议是对类型的一组抽象要求 我们可以通过提供所有要求的实现 来声明某个类型符合协议 在本例中 StringIdentifiable 是一个具有单个要求的协议 User 类型通过在扩展中 提供 identifier 属性的 实现来符合 StringIdentifiable 协议 扩展是我最喜欢的 Swift 功能之一 你可以使用扩展 为任何类型添加方法、属性 以及协议符合性 无论这个类型是在哪里定义的 Swift 标准资料库中的集合类型 是可以使用协议进行抽象处理的 类型系列的一个很好示例 你已经对 Swift 的集合类型 Array 和 Dictionary 有所了解 但还有很多其他类型 Set 是另一种集合 表示无序的唯一元素列表 String 也是 Swift 中的集合 因为它们包含 Unicode 字符列表 所有符合 Collection 协议的 类型都有一些共同特征 例如 你可以使用 for 循环 遍历任何符合 Collection 协议的类型的内容 你还可以使用索引 来访问集合的元素 Swift 标准资料库 附带了许多标准 算法的实现 可用于任何集合 如果你以前接触过函数式编程 那么 map、filter 和 reduce 等 算法看起来会比较熟悉 Swift 还针对闭包 提供了一种特殊的简写语法 从而让这些算法用起来得心应手 在没有明确命名参数的闭包中 以美元符号为前缀的变量 会匿名表示参数 这样就能轻松编写简洁的代码 下面我们运用一下 Swift 的集合算法 我的服务器具有一项功能 它可以显示你好友的好友 来查找你可能认识的人 我可以使用集合算法 来计算这组用户 这是 UserStore 类 其中封装了一个用户字典 还有一些按用户名 查找用户的现有方法 我将添加一个用于查询 friendsOfFriends 的方法 首先 它需要查找原始用户 接下来 我将创建一个 Set 其中包含原始用户及其好友的 用户名 这个集合包含我们要 从结果中排除的用户名 现在 我将使用函数式编程 来构建这个方法的结果 首先 我想将好友的用户名映射到 User 结构体的实例 这样我就可以访问他们的好友了 我使用 compactMap 查找了 每个好友的 User 结构体 并删除所有为 nil 的 User 结构体 接下来 我们要收集 用户好友的所有好友
flatMap 会将这些好友列表 串联成一个新数组 最后 我们需要排除 原始用户及其好友 filter 会删除 排除集合中的用户名 我们就快完成了 但结果存在一个问题 即 它可能会包含重复的用户名 标准资料库中 没有哪种算法可用于处理结果 并且只返回结果中的唯一元素 但我自己可以 使用泛型轻松实现一种算法 我将扩展 Collection 以添加一个名为 uniqued 的方法 uniqued 的实现方法应该很简单 首先 我可以利用 集合的内容创建一个 Set 然后将 Set 转换回 Array 但这并不完全可行 因为 Set 类型要求 自身储存的元素 符合 Hashable 协议 这很合理 因为 Set 依靠 哈希算法来高效地存储值 为了满足这一要求 我们可以 将 Collection 的扩展 限制为只包含 符合 Hashable 的元素集合 现在 调用 uniqued 就大功告成了
只需几行代码 我们就构建了一个适用于 任何集合类型的实用算法 由于 Swift 协议的灵活性 现在包含 uniqued 方法的 类型集 并不局限于某些类层次结构 你可以使用协议和泛型 实现更多操作 如果你对这方面感到好奇 建议你观看 “采用 Swift 泛型”和 “使用 Swift 设计协议接口” 在继续构建 HTTP 服务器之前 我想先介绍一个更重要的 Swift 概念 那就是并发 在 Swift 中 并发的 基本单位是任务 它代表一个独立的并发执行上下文 任务是轻量级的 因此你可以创建多个任务 你可以等待任务结束以获取结果 或者如果某个任务的工作 变得不再必要 你可以将它取消 任务可以并发执行 这使它们非常适合处理 服务器收到的 HTTP 请求 任务运行时可能会执行异步操作 比如从磁盘读取数据 或向其他服务发送消息并等待响应 任务在等待异步操作 完成时会暂停运行 以将 CPU 让给 其他有工作要执行的任务 为了在代码中模拟任务暂停 Swift 使用了 async/await 语法 可能会暂停的函数 用 async 关键字标记 在调用 async 函数时 await 关键字用于表明 这一行可能会发生暂停 我们在服务器中 使用 Swift 的并发功能 我将继续在服务器开发环境中 使用 VSCode 构建 我们之前开发的软件包 我更新了软件包 为服务器创建了一个新目标 它依赖于 Hummingbird 这是一个开源的 HTTP 服务器框架 Hummingbird 负责 侦听请求和发送响应 这样我就可以专注于 开发应用程序逻辑 我已经编写了最基本的代码 来开始侦听连接 但它们还不能发出任何请求 请求处理程序需要 引用 UserStore 为方便操作 我将扩展 UserStore 来添加静态实例 以便在不同请求之间进行共享 看起来 这段代码存在问题 访问全局 UserStore 变量 可能会引发数据争用 因为 UserStore 不是 Sendable 我们来深入了解一下这意味着什么 假设服务器收到 两个同时发送的请求 与第一个请求关联的任务 需要查找用户 而另一个任务正在创建新用户 由于 UserStore 是共享的可变状态 这些任务可能会访问 不同线程上的同一内存 这就是数据争用 可能会导致 崩溃或不可预测的行为 避免代码中出现数据争用非常重要 这就是为什么在 Swift 6 语言 模式下 程序的数据争用安全性 在编译时就得到了全面验证 实现数据争用安全性的方法之一是 要求并发域之间 共享的值是 Sendable 类型 Sendable 值可以避免 对它的状态进行并发访问 例如 如果一个类型 在读写可变状态时获得了锁定 则可能属于 Sendable 类型 如果编译器提示 UserStore 全局变量不安全 那是因为 UserStore 没有确认为 Sendable 要使 UserStore 变为 Sendable 我们可以手动为它添加同步功能 但 Swift 提供了一个更便捷的功能 来实现这一点 那就是 Actor Actor 与类相似 因为它们也是引用类型 可以封装共享的可变状态 但是 Actor 可以序列化访问 来自动保护它们自身的状态 一个 Actor 一次只允许 执行一个 Task 从 Actor 的情境外部 调用 Actor 的方法属于异步操作 现在我们对这个错误的含义 有了更多的了解 将 UserStore 设为 Actor 可以确保 对 UserStore 的并发访问的安全性
既然访问已经同步 那么错误就不复存在了 我们可以继续编写 HTTP 请求处理程序 我将添加一个 friendsOfFriends 路由 它会将用户名作为参数 并返回一个字符串数组 处理程序是一个闭包 Hummingbird 将在独立的 Task 上运行它 由于 UserStore 是一个 Actor 我们可以在这个闭包中 从不同的并发域对它进行访问 访问是异步的 我们需要使用 await 关键字
接下来 让我们用 curl 向服务器 发送一个请求来快速测试处理程序
这就是我所期待的响应 得益于 Swift 6 完善的 数据争用保护功能 我们可以确信服务器能够 正确处理并发 我们介绍了在 Swift 中 编写并发代码的基础知识 如 Tasks、async/await 和 actors 但要探索的内容还有很多 首先 你可以观看 “探索 Swift 的结构化并发”
我们要介绍的 最后一类 Swift 功能 与扩展语言有关 资料库作者通常使用 这些强大的功能来构建表现力强、 类型安全的 API 并消除 应用程序中的模板代码 第一项功能是属性包装器 这些包装器类型会封装 用于管理存储值的逻辑 通过拦截读取和写入属性的调用 它们实现了可重复使用的逻辑 只需一个简单的注解 就能应用于属性 在示例中 swift-argument-parser 软件包 中的参数属性包装器 已应用于 username 属性 参数包装器指定 这个属性存储命令行参数的值 让我们来看看实际操作 我在软件包中 为命令行实用程序 创建了一个新目标 新目标依赖于 ArgumentParser 软件包 用于解析命令行参数 在主文件中 有一个符合 AsyncParsableCommand 的类型 它为我的工具所接受的参数 提供了顶级配置
让我们来看看实际操作
参数解析器为我的工具生成了 格式精美的帮助信息 不过到目前为止 这个工具唯一 能执行的操作是打印这段说明信息 我们可以添加首个子命令 来改变这一点 首先 我可以创建一个符合 AsyncParsableCommand 的 新结构体 这个命令将使用 我们之前建立的服务器路由 来请求获取用户的好友的好友列表 我需要将它注册为子命令 这个命令需要一个用户名参数 我利用参数属性包装器 为这个参数注释了属性 接下来 我需要填写 run 方法的实现 我将利用自己编写的 Request 工具来封装 它将要发送的 HTTP 请求 它使用命令的相对路径、 响应中预期的数据类型 以及一些字典形式的参数 进行初始化 由于这是一项 HTTP get 操作 调用 get 方法可以执行请求 由于发送的是网络请求 因此这个方法属于异步操作 而当前的 Task 应在等待响应时遭到暂停 我们来尝试运行一下
我好像忘记指定用户名了 参数解析器自动 生成了一些有用的输出 来说明缺失的参数 我们再次运行一下 看看如果 我指定 Alice 会得到什么响应
太棒了 命令正在运行 参数属性包装器 让构建工具命令变得非常简单 它也是 Swift 所能提供的 极具表现力的 API 的一个绝佳例子 资料库开发者可以利用的 另一种语言工具是结果构建器 借助结果构建器 你能够 以声明方式表现复杂的值 结果构建器 API 可接受闭包 其中使用了一种特殊的轻量级语法 以逐步生成结果值 我们已使用这项功能 构建原生 UI 框架、 网页生成器等 在 Swift 标准资料库中 正则表达式构建器可利用这项功能 为简洁的正则表达式字符串 提供了一种易于阅读的替代方式 除了属性包装器和结果构建器之外 宏是另一种非常灵活的工具 宏是可充当编译器插件的 Swift 代码 将语法树作为输入 并将转换后的代码作为输出返回 如果你想详细了解 结果构建器 请观看 “使用结果构建器 在 Swift 中编写 DSL” 要了解宏 请观看 “深入了解 Swift 宏” 以上就是有关 Swift 的简要介绍 无论你是刚接触这门语言 还是已经使用了一段时间 我都希望你能 积极运用所学到的知识 并使用 Swift 的独特功能 构建出真正酷炫的 App 在编写代码时 你可以寻找机会 挑选合适的工具来完成相应的工作 你可能会使用值类型而不是类来建模 使用泛型来全面泛化算法 或者使用 Actor 来解决数据争用问题 Swift 拥有编写简练而强大的代码 所需的所有工具 感谢大家今天的参与 祝你在 WWDC 度过美妙的时光
-
-
1:49 - Integer variables
var x: Int = 1 var y: Int = x x = 42 y
-
3:04 - User struct
struct User { let username: String var isVisible: Bool = true var friends: [String] = [] } var alice = User(username: "alice") alice.friends = ["charlie"] var bruno = User(username: "bruno") bruno.friends = alice.friends alice.friends.append("dash") bruno.friends
-
3:05 - User struct error handling
struct User { let username: String var isVisible: Bool = true var friends: [String] = [] mutating func addFriend(username: String) throws { guard username != self.username else { throw SocialError.befriendingSelf } guard !friends.contains(username) else { throw SocialError.duplicateFriend(username: username) } friends.append(username) } } enum SocialError: Error { case befriendingSelf case duplicateFriend(username: String) } var alice = User(username: "alice") do { try alice.addFriend(username: "charlie") try alice.addFriend(username: "charlie") } catch { error } var allUsers = [ "alice": alice ] func findUser(_ username: String) -> User? { allUsers[username] } if let charlie = findUser("charlie") { print("Found \(charlie)") } else { print("charlie not found") } let dash = findUser("dash")!
-
11:01 - SocialGraph package manifest
// swift-tools-version: 6.0 import PackageDescription let package = Package( name: "SocialGraph", products: [ .library( name: "SocialGraph", targets: ["SocialGraph"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-testing.git", branch: "main"), ], targets: [ .target( name: "SocialGraph"), .testTarget( name: "SocialGraphTests", dependencies: [ "SocialGraph", .product(name: "Testing", package: "swift-testing"), ]), ] )
-
11:12 - User struct
/// Represents a user in the social graph. public struct User: Equatable, Hashable { /// The user's username, which must be unique in the service. public let username: String /// Whether or not the user should be considered visible /// when performing queries. public var isVisible: Bool /// The usernames of the user's friends. public private(set) var friends: [String] public init( username: String, isVisible: Bool = true, friends: [String] = [] ) { self.username = username self.isVisible = isVisible self.friends = friends } /// Adds a username to the user's list of friends. Throws an /// error if the username cannot be added. public mutating func addFriend(username: String) throws { guard username != self.username else { throw SocialError.befriendingSelf } guard !friends.contains(username) else { throw SocialError.alreadyFriend(username: username) } friends.append(username) } }
-
12:36 - Classes
class Pet { func speak() {} } class Cat: Pet { override func speak() { print("meow") } func purr() { print("purr") } } let pet: Pet = Cat() pet.speak() if let cat = pet as? Cat { cat.purr() }
-
12:59 - Automatic reference counting
class Pet { var toy: Toy? } class Toy {} let toy = Toy() let pets = [Pet()] // Give toy to pets for pet in pets { pet.toy = toy } // Take toy from pets for pet in pets { pet.toy = nil }
-
13:26 - Reference cycles
class Pet { weak var owner: Owner? } class Owner { var pets: [Pet] }
-
14:20 - Protocols
protocol StringIdentifiable { var identifier: String { get } } extension User: StringIdentifiable { var identifier: String { username } }
-
15:21 - Common capabilities of Collections
let string = "🥚🐣🐥🐓" for char in string { print(char) } // => "🥚" "🐣" "🐥" "🐓" print(string[string.startIndex]) // => "🥚"
-
15:31 - Collection algorithms
let numbers = [1, 4, 7, 10, 13] let numberStrings = numbers.map { number in String(number) } // => ["1", "4", "7", "10", "13"] let primeNumbers = numbers.filter { number in number.isPrime } // => [1, 7, 13] let sum = numbers.reduce(0) { partial, number in partial + number } // => 35
-
15:45 - Collection algorithms with anonymous parameters
let numbers = [1, 4, 7, 10, 13] let numberStrings = numbers.map { String($0) } // => ["1", "4", "7", "10", "13"] let primeNumbers = numbers.filter { $0.isPrime } // => [1, 7, 13] let sum = numbers.reduce(0) { $0 + $1 } // => 35
-
16:13 - Friends of friends algorithm
/// An in-memory store for users of the service. public class UserStore { var allUsers: [String: User] = [:] } extension UserStore { /// If the username maps to a User and that user is visible, /// returns the User. Returns nil otherwise. public func lookUpUser(_ username: String) -> User? { guard let user = allUsers[username], user.isVisible else { return nil } return user } /// If the username maps to a User and that user is visible, /// returns the User. Otherwise, throws an error. public func user(for username: String) throws -> User { guard let user = lookUpUser(username) else { throw SocialError.userNotFound(username: username) } return user } public func friendsOfFriends(_ username: String) throws -> [String] { let user = try user(for: username) let excluded = Set(user.friends + [username]) return user.friends .compactMap { lookUpUser($0) } // [String] -> [User] .flatMap { $0.friends } // [User] -> [String] .filter { !excluded.contains($0) } // drop excluded .uniqued() } } extension Collection where Element: Hashable { func uniqued() -> [Element] { let unique = Set(self) return Array(unique) } }
-
19:23 - async/await
/// Makes a network request to download an image. func fetchUserAvatar(for username: String) async -> Image { // ... } let avatar = await fetchUserAvatar(for: "alice")
-
19:43 - Server
import Hummingbird import SocialGraph let router = Router() extension UserStore { static let shared = UserStore.makeSampleStore() } let app = Application( router: router, configuration: .init(address: .hostname("127.0.0.1", port: 8080)) ) print("Starting server...") try await app.runService()
-
20:20 - Data race example
// Look up user let user = allUsers[username] // Store new user allUsers[username] = user // UserStore var allUsers: [String: User]
-
22:24 - Server with friendsOfFriends route
import Hummingbird import SocialGraph let router = Router() extension UserStore { static let shared = UserStore.makeSampleStore() } router.get("friendsOfFriends") { request, context -> [String] in let username = try request.queryArgument(for: "username") return try await UserStore.shared.friendsOfFriends(username) } let app = Application( router: router, configuration: .init(address: .hostname("127.0.0.1", port: 8080)) ) print("Starting server...") try await app.runService()
-
23:27 - Property wrappers
struct FriendsOfFriends: AsyncParsableCommand { @Argument var username: String mutating func run() async throws { // ... } }
-
23:57 - SocialGraph command line client
import ArgumentParser import SocialGraph @main struct SocialGraphClient: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "A utility for querying the social graph", subcommands: [ FriendsOfFriends.self, ]) } struct FriendsOfFriends: AsyncParsableCommand { @Argument(help: "The username to look up friends of friends for") var username: String func run() async throws { var request = Request(command: "friendsOfFriends", returning: [String].self) request.arguments = ["username" : username] let result = try await request.get() print(result) } }
-
26:07 - Result builders
import RegexBuilder let dollarValueRegex = Regex { // Equivalent to "\$[0-9]+\.[0-9][0-9]" "$" OneOrMore(.digit) "." Repeat(.digit, count: 2) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。