Combine: 概览

6,002 阅读4分钟

异步编程

在一种简单的单线程语言中,程序按顺序逐行执行。 例如,在伪代码中:

begin
  var name = "Tom"
  print(name)
  name += " Harding"
  print(name)
end

同步代码很容易理解,并且数据的状态也很清晰。通过单线程执行,始终可以确定数据的当前状态。 在上面的示例中,第一次的打印结果总是“Tom”,第二次的打印结果总是“Tom Harding”。

现在,想象一下使用异步事件驱动的多线程语言来编写程序,就像使用 Swift 和 UIKit 的 iOS 程序一样,会发生什么:

--- Thread 1 ---
begin
  var name = "Tom"
  print(name)

--- Thread 2 ---
name = "Billy Bob"

--- Thread 1 ---
  name += " Harding"
  print(name)
end

在这里,Thread 1 将name的值设置为“Tom”,然后给它追加“Harding”。但是因为Thread 2可以同时执行,所以程序可能会在 name 的两个突变之间运行,并将其设置为另一个值“Billy Bob”。当代码在不同的内核上并发运行时,很难说哪部分代码会先修改共享状态。

运行此代码时究竟会发生什么,最后会得到什么样的结果,取决于系统负载,并且每次运行该程序时都可能会看到与之前一次不一样的结果。在异步并发的世界里,应用程序的可变状态管理等同于系统进行的一项加载任务。而 异步编程的本质是响应未来发生的事件流。

常见的使用异步编程方式的场景:

  • 网络请求
  • 等待响应用户点击事件
  • 进行 UI 动画并在动画结束之后触发一些代码
  • 图片下载和保存
  • 传递数据到机器学习模型,识别其中内容

传统的 Cocoa 和 UIKit 中提供了一系列的异步 API,它们往往以下面的某种形式出现:

  • 闭包回调:事件比较单纯,不需要关心过程中的细节,只需要响应结果。比如弹窗
  • delegate:希望控制更多细节,或者需要关心多种异步事件。比如 UITableViewDelegate,闭包 API 相比 delegate 存在一些设计上的困难
  • 通知:触发时机不确定的,可能长期存在的行为对应的事件。比如退出登录

在异步编程中,不论是用闭包,delegate 还是 notification,实际上都是在当前的时间节点上预先设置好特定的逻辑,去处理未来会发生的事件。

Combine框架

响应式异步编程

事件是异步编程中的核心。在客户端的开发中,异步编程的关注点就是如何处理这些事件,并通过响应这些事件来改变程序状态,最终影响用户界面和体验。

响应式异步编程的核心就是当异步操作中的某个事件发生时,要将这个事件和必要相关数据发布出来,由对这个事件感兴趣的代码订阅它,并在收到发布后进行需要的操作。

通过对事件处理的操作进行组合,来对异步事件进行自定义处理。

Combine 提供了一组声明式的 Swift API,来处理随时间变化的值。这些值可以代表用户界面的事件,网络的响应,计划好的事件,或者很多其他类型的异步数据。你可以为给定的事件创建单个处理链,而不用实现多个委托或者闭包回调。处理链的每个部分都是一个Combine操作符,用来对从上一步接收到的元素执行不同的操作。

核心概念

  • Publisher
  • Operator
  • Subscriber

关于Combine的这几个核心概念,鉴于篇幅问题,后面再做详细的介绍。这里先做一个大概的说明。

PublisherSubscriber 分别代表事件的发布者和订阅者,它们都被定义为protocol。

Publisher和Subscriber总是成对出现的,如果一个Publisher没有一个Subscriber对其进行订阅,那么它将不会发出任何事件,而且Publisher会发出多少事件,发出什么样的事件也由Subscriber来控制,这是一种Back-pressure(背压) 机制。

Operator兼具Publisher和Subscriber的特性,它同时遵循Subscriber和 Publisher协议,用来对上游数据进行操作。

每个 Operator 的行为模式都一样:它们使用上游 Publisher 所发布的数据作为输入,以此产生的新的数据,然后自身成为新的 Publisher,并将这些新的数据作为输出,发布给下游。

我们可以组合使用不同的操作符,使它们形成一条处理链条:当链条最上端的 Publisher 发布某个事件后,链条中的各个 Operator 对事件和数据进行处理,最后得到subscriber想要的数据。

参考

Combine: Asynchronous Programming with Swift

SwiftUI和Combine编程