大多数浏览器和
Developer App 均支持流媒体播放。
-
使用参数包泛化 API
Swift 参数包是一个强大的工具,可以扩展泛型代码的可能性,同时还能帮助开发者简化常见的泛型模式。我们将展示如何对泛型代码中的类型和参数数量进行抽象,来避免使用超载。为了更好理解本讲座,我们建议开发者先查看 WWDC22 的 “采用 Swift 泛型”讲座。
章节
- 0:00 - Introduction
- 0:52 - What parameter packs solve
- 4:08 - How to read parameter packs
- 12:06 - Using parameter packs
- 17:22 - Wrap up
资源
相关视频
WWDC23
WWDC22
-
下载
♪ ♪
Sophia:大家好 欢迎来到 “使用参数包泛化 API”讲座 我叫 Sophia 我来自 Swift 编译器团队 今天我为你介绍 Swift 参数包 以及它们如何为泛型编程 提供新的灵活性
本讲座包含进阶内容 并以现有的泛型系统为基础 如果你对泛型系统还不熟悉 我强烈推荐你先查看 WWDC22 的 “采用 Swift 泛型”讲座 今天 我将带你了解参数包 可以解决的问题类型 以及当你在库中遇到参数包时 应如何理解它们 最后 我会详细介绍要如何 实现你自己的使用参数包的代码 在我介绍参数包之前 重要的是 要先了解它们为什么存在 因此 我将从泛型 和 variadic 开始介绍 你编写的代码 从根本上由两个类别组成: 值和类型 你可以通过编写 接受不同值作为参数的函数 来对值进行抽象 例如 radians(from:) 函数 作为输入可以接受 任何表示度数的 Double 值 而输出时将返回 表示弧度的新 Double 值 你可以通过编写 接受不同类型参数的泛型代码 来对类型进行抽象 例如 标准库中的 Array 类型 被设计用于储存各种形式的数据 它有一个 Element 类型参数 该参数是一个占位符 用于表示你将为 Array 的 给定实例使用的具体类型 在这两个例子中 具体值或具体类型 都作为参数被传递给抽象 大多数泛型代码 都会对类型和值进行抽象 为了探索这一点 我要编写一些代码 来将查询发送到服务器
它的基本实现将接收 某种 Payload 类型的 Request 然后将其作为查询传递给服务器 最后返回 Payload 类型的 服务器响应 该函数有一个参数 但我想支持在同一调用中 查询多个请求 为了能够拥有可变数量的参数 我们将使用 variadic 参数 variadic 参数 可以让函数灵活地接受 单个类型的任意数量的参数 但 variadic 参数 也有其局限性 例如 你可能希望将给定参数映射到 一个长度与参数数量相同的元组中 然而 使用 variadic 参数的话 你就无法声明 一个基于参数长度的返回类型 此外 如果不使用类型擦除 variadic 参数就无法接受不同的类型 因此无法保留 每个参数的具体静态类型信息 对于泛型系统和 variadic 参数 我们所缺乏的 是既能保留类型信息 又能改变参数数量的能力 如今做到这一点的唯一方法是超载 它可以迫使你选择 支持的参数数量的上限 我在想 两个参数够吗?可能不够 最好能处理多达三个参数 但如果我们需要四个呢? 这种超载模式 以及它的局限性 在概念上 可以处理不同数量 类型参数的 API 中普遍存在 这种方法的缺点是冗余 但更重要的是 它会迫使我们为可支持的参数 选择一个任意的数量上限 当参数超过我们选择的上限 将导致编译器出错 提示我们参数超限 这正是参数包所能解决的问题 如果你发现自己 陷入了这种超载模式 这就说明你非常需要使用参数包 在 Swift 5.9 中 泛型系统将提供 对参数长度进行抽象的一级类支持 这是通过一个叫“参数包”的 新结构来实现的 下面我将介绍 当你在 API 中看到参数包时 应该如何理解它们 在代码中 你大多数情况下 会处理单个类型或值 参数包可以容纳 任意数量的类型或值 并将它们打包在一起 作为参数传递给函数 存放单独类型的参数包叫做类型包 例如 一个类型包可以包含 三种单独的类型: Bool、Int 和 String 存放单独值的参数包叫做值包 例如 一个值包可以包含 三种单独的值:true、数字 10 以及空字符串 类型包和值包是一起使用的 类型包为值包中的 每个独立值提供相应的独立类型 相对应的类型和值在各自的包中 处于相同的位置 在位置 0 值 true 的类型是 Bool 在位置 1 整数字面量 10 的类型是 Int 而在位置 2 空字符串字面量的类型是 String 参数包允许你编写一段泛型代码 该代码适用于包中的每个单独元素 这个概念可能听起来很熟悉 因为当你在 Swift 中 使用 Collections 时 你已经习惯了编写一段代码 来处理不同的独立元素 编写此类代码的方式是迭代 例如 for-in 循环的主体 会对数组的每个单独元素进行操作 参数包与集合不同的地方在于 包中的每个元素 都具有不同的静态类型 并且你可以在类型级别处理参数包 通常 你可以通过在尖括号内 声明类型参数来编写 适用于不同具体类型的泛型代码 在 Swift 5.9 中 你可以使用关键字“each” 声明一组类型参数 函数不再接受单个类型参数 而是接受你想要查询的 每个 Payload 类型 这被称为类型参数组 为了使类型包和值包的名称 读起来更自然 请使用单数命名约定 比如使用“each Payload” 而不是“each Payloads” 使用参数包的泛型代码 可以使用重复模式 对每个 Payload 进行单独操作 重复模式使用 “repeat”关键字表示 后面跟随一个称为模式类型的类型 该模式将包含一个或多个 对参数包元素的引用 “repeat”表示模式类型 将对给定参数包中的 每个元素重复使用 “each”是一个占位符 在每次迭代时 都会被替换为单独的数据包元素 让我们通过包含 Bool、Int 和 String 的具体类型包 来看看这种替换是如何工作的 模式将被重复三次 并在每次重复过程中 将占位符“each Payload” 替换为包中的具体类型 结果是一个用逗号分隔的类型列表: Bool 的 Request、Int 的 Request 和 String 的 Request 由于重复模式会生成 逗号分隔的类型列表 因此它们只能用在 本来就接受逗号分隔列表的位置 这包含了被括号括起来的类型 即要么是元组类型 要么是单个类型 此外 它们可以用于函数参数列表 而且重复模式 可以用于泛型参数列表 将重复模式用作函数参数的类型 将使该函数参数成为值参数包 这使调用者能够传入 任意数量的 Request 实例 并且参数值将被收集到一个包中 并传递给函数 以上就是参数包的基本概念 及其所使用的语法 接下来 为了展示它们 如何简化和扩展 API 的功能 让我们接着看我们的查询 API 我已经添加了多个泛型超载 以提供 可变的请求参数和相应的返回类型 每个超载的声明 都遵循一个可预测的模式 每个超载都分别有 类型参数 1、2、3 和 4 每个超载将每个类型参数映射到 参数列表中该类型的 Request 而每个超载包含 返回类型中的每个类型参数的列表 使用参数包 这四个超载可以折叠为 一个单独的函数 让我们先考虑类型参数的声明 然后是函数参数列表 最后是返回类型 每个类型参数都可以 折叠成一个类型参数包 每个单独的 Request 参数 都可以折叠为一个值参数包 而返回类型可以折叠成一个元组 该元组是通过重复 每个 Payload 类型构造的 现在你的查询函数 可以处理任意数量的请求参数 由于函数参数和返回类型 都是类型参数包 “each Payload”的依赖类型 所以函数的值参数包的长度将始终 与返回元组中的元素数量相等 现在我在这个 API 中 采用了参数包 你可以使用一个参数、 三个参数或任意数量的参数 来调用这个单个查询函数 参数包会以相同的方式 处理所有参数长度 让我们重点关注三个参数的调用 具体参数包是从 调用站点的参数中推断出来的 占位符“each Payload”的 每个具体类型 都会从参数列表中 被收集到一个类型包中 然后具体类型包 会被替换以生成返回类型 “each Payload”会出现在 参数列表和返回类型中 具体类型包“Int、String、Bool” 在两个地方都被替换 导致模式重复三次 最终运行的代码相当于对类型包的 所有三种类型进行迭代 现在让我们回到我们的查询 API 看看如何向参数包添加约束 假设我们查询的 有效负载应该是 Equatable 通过在类型参数包后面添加冒号 和协议名称 Equatable 我们可以要求 Payload 包中的 每个元素都遵守 Equatable 更通用的要求 可以通过“where”子句来声明 就像普通的泛型代码一样 之前我们介绍了 参数包可以包含零个或多个参数 你可能会觉得这个服务器查询 API 没有特定的理由接受零个参数 好消息是 我们可以用一种 简单的方法要求最小的参数长度 在这个例子中 我想至少需要一个参数 让函数有事情可做 为此 我在类型参数包之前 添加一个常规类型参数 并在值参数包之前 添加一个相应的值参数 类型参数包上的任何约束 都应适用于新的类型参数 在本例中 就是 Equatable 的一致性 现在你的函数的调用者 必须提供至少一个参数 目前为止 我们已经 介绍了一些基础知识 包括参数包可以解决什么问题 以及如何理解 API 中的参数包 接下来 让我们来看一下 如何实现使用参数包的代码 我们将使用参数包构建 服务器查询的实现 查询函数接受一个值包 其中每个单独的元素都是 对类型包中每个元素的 Request Request struct 有一个 名为 Payload 的单个类型参数 和一个返回 Payload 实例的 evaluate 方法 查询函数的主体将在 “item”值包上进行操作 在查询的主体中 我想为值包中的每个项 调用 evaluate 方法 你可以使用重复模式来表达 重复模式在类型层级和值层级 会使用相同的语法来表达 在值层级 “repeat”关键字后面 会跟着模式表达式 模式表达式将包含一个或多个值包 该值包会对其包含的 每个值进行迭代 并且每个值都会计算一次表达式 为了生成包含在元组中的 所有求值结果的列表 你可以用括号将模式表达式括起来 如果传递给函数的值包为空 则结果将为空元组 如果值包只有单个元素 则结果将是一个单个值 如果值包有多个元素 则结果将是一个元组 就是这样 现在 我们有一个查询函数 它会接受结果值包 对每个单独的请求进行求值 并将每个请求的结果 以元组的形式返回 这就是你在代码中 使用参数包的基础 与之前使用多个超载 而非参数包的示例相比 这种方法的代码量仍然要少得多 而之前的示例甚至都还没有实现 使用参数包让维护变得更容易了 而且重复代码模式 让经常产生的错误也消失了 现在让我们增加一点灵活性 我将重构我的示例 使查询 API 能够存储状态 并使每个请求的求值 具有不同的输入和输出类型 以及在参数包迭代过程中 管理控制流程 我将把查询函数移动到 一个 Evaluator struct 内 并将类型参数包 从查询方法提升到 Evaluator 类型 Evaluator struct 可以将请求包 存储在一个存储属性中 方法是将其括在括号中 使其成为元组值 如果是一个给定的 具体 Payload 类型参数包 “item”变量要么是单个请求 要么是包含每个请求的元组 下一步 我将把 Request 从一个 struct 变为一个协议 该协议具有一个 名为 Output 的关联类型 然后我将为 Request 协议 添加另一个关联类型 名为 Input 接下来 我将更新 Request 中的 evaluate 方法 使其参数成为协议的 Input 类型 这使得该方法的返回类型 与参数类型的返回类型不同 在此之后 我会更新 Evaluator 要求所有 Payload 类型 遵守 Request 协议 并相应更新“item”储存属性 使其直接成为 “each Payload”类型 然而这个时候 “Payload”这个名字 已经不再适合作为 Evaluator 类型参数包的名称 Payload 不再表示 Request 中所包含的内容 而是遵守整个 Request 协议 因此 我们将 Payload 的名称 更改为 Request 并把协议的名称更改为 RequestProtocol 查询方法现在可以接受每个 Request 的 Input 类型的参数包 并且它将返回每个 Request 的 Output 类型的列表 最后 查询方法的新参数“input” 只需传递给每个项的 evaluate 方法的调用即可 现在 我们可以从服务器返回 不同类型的响应 而不是我们在查询中 包含的数据类型 我们可以知道方法的值参数包的长度 将与返回的值包的长度匹配 因为它们的类型都基于 Evaluator 的类型包 存储属性“item”中 参数的长度也是如此 鉴于使用参数包是一种迭代形式 如果你希望提前退出迭代 可能会对控制流产生疑问 也许这种情况只有在 每个查询都成功时 一组查询的结果才会生效 可以通过抛出错误来做到这一点 在我们的例子中 你可以将 RequestProtocol 的 evaluate 方法 更新为一个抛出函数 并将 Evaluator 的 查询方法的返回类型 修改为可选项 你可以将查询方法的主体 放入一个 do-catch 语句中 在 do 子句中放置 return 语句 并在 catch 子句中返回 nil 现在 所有单独查询的求值 都可以在需要的情况下 停止对所有查询的迭代 在本次讲座中 我们介绍了如何利用参数包 在泛型代码中 对类型和参数数量进行抽象 我们演示了如何使用参数包 来简化和消除你的代码中的限制 通过编写单个泛型实现 就可以取代 以前需要多个超载的情况 最后 通过利用参数包 我们用代码实现了 向服务器发送查询 要进一步了解有关泛型的更多信息 请查看 WWDC22 的 “采用 Swift 泛型”讲座 要进一步了解有关协议 和类型擦除的更多信息 请查看 WWDC22 的 “使用 Swift 设计协议接口”讲座 Swift 参数包是一个强大的工具 它可以扩展泛型代码的可能性 同时还能帮助你 简化常见的泛型模式 我们已经等不及想看到 你用参数包构建的成果了 感谢你观看本次讲座
-
-
1:13 - radians function
func radians(from degrees: Double) -> Double
-
1:26 - Array type
struct Array<Element>
-
1:48 - radians function and Array type with concrete expressions
func radians(from degrees: Double) -> Double radians(from: 180) struct Array<Element> Array<Int>
-
2:04 - generic query
func query<Payload>(_ item: Request<Payload>) -> Payload
-
2:22 - variadic query
func query(_ item: Request...)
-
3:16 - generic query
func query<Payload>(_ item: Request<Payload>) -> Payload
-
3:23 - two query overloads
func query<Payload>( _ item: Request<Payload> ) -> Payload func query<Payload1, Payload2>( _ item1: Request<Payload1>, _ item2: Request<Payload2> ) -> (Payload1, Payload2)
-
3:28 - three query overloads
func query<Payload>( _ item: Request<Payload> ) -> Payload func query<Payload1, Payload2>( _ item1: Request<Payload1>, _ item2: Request<Payload2> ) -> (Payload1, Payload2) func query<Payload1, Payload2, Payload3>( _ item1: Request<Payload1>, _ item2: Request<Payload2>, _ item3: Request<Payload3> ) -> (Payload1, Payload2, Payload3)
-
3:31 - four query overloads with extra argument error
func query<Payload>( _ item: Request<Payload> ) -> Payload func query<Payload1, Payload2>( _ item1: Request<Payload1>, _ item2: Request<Payload2> ) -> (Payload1, Payload2) func query<Payload1, Payload2, Payload3>( _ item1: Request<Payload1>, _ item2: Request<Payload2>, _ item3: Request<Payload3> ) -> (Payload1, Payload2, Payload3) func query<Payload1, Payload2, Payload3, Payload4>( _ item1: Request<Payload1>, _ item2: Request<Payload2>, _ item3: Request<Payload3>, _ item4: Request<Payload4> ) -> (Payload1, Payload2, Payload3, Payload4) let _ = query(r1, r2, r3, r4, r5)
-
5:52 - for-in loop over requests
for request in requests { evaluate(request) }
-
8:41 - four query overloads
func query<Payload>( _ item: Request<Payload> ) -> Payload func query<Payload1, Payload2>( _ item1: Request<Payload1>, _ item2: Request<Payload2> ) -> (Payload1, Payload2) func query<Payload1, Payload2, Payload3>( _ item1: Request<Payload1>, _ item2: Request<Payload2>, _ item3: Request<Payload3> ) -> (Payload1, Payload2, Payload3) func query<Payload1, Payload2, Payload3, Payload4>( _ item1: Request<Payload1>, _ item2: Request<Payload2>, _ item3: Request<Payload3>, _ item4: Request<Payload4> ) -> (Payload1, Payload2, Payload3, Payload4)
-
9:37 - parameter pack query interface
func query<each Payload>(_ item: repeat Request<each Payload>) -> (repeat each Payload)
-
10:01 - parameter pack query with single argument call
func query<each Payload>(_ item: repeat Request<each Payload>) -> (repeat each Payload) let result = query(Request<Int>())
-
10:08 - parameter pack query with single and triple argument calls
func query<each Payload>(_ item: repeat Request<each Payload>) -> (repeat each Payload) let result = query(Request<Int>()) let results = query(Request<Int>(), Request<String>(), Request<Bool>())
-
10:15 - parameter pack query with triple argument call
func query<each Payload>(_ item: repeat Request<each Payload>) -> (repeat each Payload) let results = query(Request<Int>(), Request<String>(), Request<Bool>())
-
10:56 - parameter pack query interface
func query<each Payload>( _ item: repeat Request<each Payload> ) -> (repeat each Payload)
-
11:03 - parameter pack query interface with conformance
func query<each Payload: Equatable>( _ item: repeat Request<each Payload> ) -> (repeat each Payload)
-
11:17 - parameter pack query interface with where clause
func query<each Payload>( _ item: repeat Request<each Payload> ) -> (repeat each Payload) where repeat each Payload: Equatable
-
11:44 - parameter pack query interface with minimum parameter count
func query<FirstPayload, each Payload>( _ first: Request<FirstPayload>, _ item: repeat Request<each Payload> ) -> (FirstPayload, repeat each Payload) where FirstPayload: Equatable, repeat each Payload: Equatable
-
13:42 - parameter pack query implementation
struct Request<Payload> { func evaluate() -> Payload } func query<each Payload>(_ item: repeat Request<each Payload>) -> (repeat each Payload) { return (repeat (each item).evaluate()) }
-
16:04 - parameter pack query implementation with different input and output types
protocol RequestProtocol { associatedtype Input associatedtype Output func evaluate(_ input: Input) -> Output } struct Evaluator<each Request: RequestProtocol> { var item: (repeat each Request) func query(_ input: repeat (each Request).Input) -> (repeat (each Request).Output) { return (repeat (each item).evaluate(each input)) } }
-
17:05 - parameter pack query implementation with control flow break
protocol RequestProtocol { associatedtype Input associatedtype Output func evaluate(_ input: Input) throws -> Output } struct Evaluator<each Request: RequestProtocol> { var item: (repeat each Request) func query(_ input: repeat (each Request).Input) -> (repeat (each Request).Output)? { do { return (repeat try (each item).evaluate(each input)) } catch { return nil } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。