「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」
随着时间的推移,许多开发人员在维护各种代码库时面临的一个挑战是如何以正确遵守所涉及的每种技术约定的方式巧妙地连接不同的框架和 API。
例如,随着世界各地的团队开始采用 Swift 5.5 的async/await驱动的并发系统,我们可能会发现自己需要创建与其他异步兼容的带有async标记的 API 版本 编程技术——例如Combine。
虽然我们已经了解了 Combine 如何与异步序列和流等并发 API 相关联,以及我们如何能够在 Combine 管道中调用带有异步标记的函数——在本文中,让我们探讨如何实现它 轻松创建任何async API 的基于组合的变体,无论它是由我们、Apple 定义还是作为第三方依赖项的一部分。
Async 特征
假设我们正在开发的应用程序包含以下 ModelLoader,可用于通过网络加载任何可解码模型。 它通过如下所示的异步函数执行其工作:
现在假设我们还想创建上述 loadModel API 的基于组合的版本,例如,以便能够在我们的代码库的特定部分中调用它,这些部分可能已经以更具反应性的风格编写使用组合框架。
我们当然可以选择专门为我们的 ModelLoader 类型编写那种兼容性代码,但是由于这是我们在使用基于组合的代码时可能会多次遇到的普遍问题,所以让我们创建一个更通用的解决方案我们将能够轻松地在我们的代码库中重用。
由于我们正在处理返回单个值或抛出错误的异步函数,让我们使用 Combine 的 Future 发布者来包装这些调用。该发布者类型是专门为这些类型的用例构建的,因为它为我们提供了一个闭包,可用于将单个 Result 报告回框架。
所以让我们继续用一个方便的初始化器来扩展 Future 类型,它可以用一个异步闭包来初始化一个实例:
创建这样的抽象(它不依赖于任何特定的用例)的强大之处在于,我们现在可以将它应用到我们想要使组合兼容的任何异步 API。 只需要几行代码调用我们希望在传递给新的 Future 初始化器的闭包中桥接的 API —— 像这样:
整洁的! 请注意,我们如何选择为基于 Combine 的版本赋予与我们的异步驱动版本相同的 loadModel 名称(因为 Swift 支持方法重载)。 但是,在这种情况下,将两者明确分开可能是个好主意,这就是为什么上述新 API 的名称中明确包含“Publisher”一词的原因。
Reactive async sequences
异步序列和流可能是 Swift 标准库最接近采用反应式编程的方式,这反过来又使这些 API 的行为与 Combine 非常相似——因为它们使我们能够随着时间的推移发出值。
事实上,在“异步序列、流和组合”一文中,我们了解了组合发布者如何使用它们的 values 属性直接转换为异步序列——但如果我们想换一种方式,转换 一个异步序列(或流)到发布者?
继续之前的 ModelLoader 示例,假设我们的加载器类还提供以下 API,它允许我们创建一个 AsyncThrowingStream,它发出从 URL 数组加载的一系列模型:
就像以前一样,与其急于编写专门将上述 loadModels API 转换为 Combine 发布者的代码,不如尝试提出一个通用抽象,只要我们想在其他地方编写类似的桥接代码,就可以重用它 我们的项目。
这一次,我们将扩展 Combine 的 PassthroughSubject 类型,它让我们可以完全控制它的值何时发出,以及何时以及如何终止。 但是,我们不会将此 API 建模为便利初始化程序,因为我们想明确说明调用此 API 实际上会使创建的主题立即开始发出值。 所以让我们把它变成一个静态工厂方法——像这样:
完成上述操作后,我们现在可以像之前的异步标记 API 一样轻松地封装基于异步流的 loadModels API ——在这种情况下,唯一需要的额外步骤是将我们的 PassthroughSubject 实例类型擦除到 AnyPublisher 中,以 阻止任何其他代码向我们的主题发送新值: