RxSwift 给 Swift 带来了原生 Reactive 编程的功能

  • Sergio De Simone
  • 薛梦迪

2016 年 4 月 27 日

话题:移动iOS语言 & 开发

RxSwift项目以将Rx编程模型应用至 Swift 中为目标,其中包含了尽可能多的抽象概念。InfoQ 采访了项目的维护者 Krunoslav Zaher。

RxSwift 是基于Observable<Element>接口的,这个接口旨在能够执行异步运算和事件流的简单组合。在 RxSwift 中,观察者和序列相同,所以可以通过Observable接口中的操作模仿在序列元素上的高阶操作,例如数据或事件流。

RxSwift 中灵活的编程模型使许多不同的用例成为可能:绑定,包括 UI 绑定;重操作;代理;KVO;通知。

接下来的这个例子展示了你如何通过rx_text操作绑定两个文本域。其中一个的值和另一个的值通过一个异步 API(WolframAlphaIsPrime)关联起来。无论前一个值何时改变,后一个的值都会通过一个异步调用来更新:

let subscription : Disposable  = primeTextField.rx_text      // type is Observable
            .map { WolframAlphaIsPrime($0.toInt() ?? 0) }       // type is Observable>
            .concat()                                           // type is Observable
            .map { "number \($0.n) is prime? \($0.isPrime)" }   // type is Observable
            .bindTo(resultLabel.rx_text)                        // return Disposable that can be used to unbind everything

// This will set resultLabel.text to "number 43 is prime? true" after
// server call completes.
primeTextField.text = "43"

// ...

// to unbind everything, just call
subscription.dispose()

InfoQ 采访了项目的维护者 Krunoslav Zaher。

你可以解释一下 Rx 编程需要什么样的观念转变吗?

这都取决于你的编程背景是什么。对于那些大部分工作经验都是使用 ObjectiveC 或 Swift(对,也包括 Swift)进行编程的人,我会说这需要他们不把它看作一种编程,而是看作一个动态系统的行为声明。

当一个人使用可观察的序列并使用操作来转化它们,事实上,他 / 她是在定义可观察序列的结果如何与那些操作应用的源序列相关联。这可能在刚开始的时候看起来很奇怪。而真正重要的是将它们看作可观察的序列。

使用不一样的、不太准确的方式思考可观察的序列会带来很多问题(流、信号、导管等)。那些东西有时候可以用来帮助新手探索 Rx,但是如果使用的时间过长,它们只会在接下来的开发中带来很多困惑。人们有时候使用它们是因为 Rx 可以用在模拟系统的多状态部分,但是那只是故事的一部分,而我建议大家不要那样思考。

使用那些项(流、信号、导管等)思考的问题在于它们带有一个隐含假定:

定义Observable时带有共享的值,并且可以从外部设定的当前值也是一样的。而Observable会被莫名其妙地直接取消,这看起来像 future 或 promise。

而事实上:

Observable只是一个如何计算序列的定义。没有计算会在序列定义后(let result =

source.map(transformObservableSequence)
)自动地执行。这和Swift中的SequenceSequence是一样的。当绑定一个观察者时(这与调用 SequenceType 中的 generate 方法相同)才会执行计算,并且可以通过处理Disposable对象的结果来取消特定的计算。Observable当然可以代表多状态的计算,它们共享潜在的绑定并source可观察的序列(使用shareReplay(1)操作等),但是这不是默认的行为。

我认为部分的问题在于也许人们一直在使用 future、promise 或其他更简单的、使用方式相似的 reactive 库,所以他们自然地认为 Rx 在其他的情况下也是同样的表现,而这对于新手来说显然是令人困惑的。

奇怪的是那些单方面的属性对刚开始使用 Rx 的人们造成了很大的问题,有时候门槛会变得太高以至于人们变的没有动力,但是从另一角度来看,这正是我个人认为 Rx 美的原因。

它美的地方在于你可以只通过一句话就能教会一个人使用 Rx:Observable<T>代表了元素类型为 T 的可观察序列(与Swift中的SequenceSequence是一样的),其中每个元素都可以调用subscribe(和SequenceSequence中的generate方法相同)方法来注册自己的接受序列元素的observer(返回信号)。

如果这方面很清楚的话,所有的其他东西都只是细节,或变得非常明显和自然。

将 Rx 看作另一个库而不是一个不同的概念 / 抽象,并且开始在 Stack

Overflow 或相似的网站上开始查阅它,将会带来许多问题。在学习 Rx 时,唯一需要学习的部分就是理解可观察序列的句子,这样的话其他的东西就很显而易见了。这时候,陡峭的学习曲线就消失了。

使用 Rx 的好处在哪里?

这里有很多的好处,我的描述中可能会跳过许多:

  • 你可以停下编程,并且开始着眼于你的程序如何表现,使你的电脑了解如何应对暂态和不能预料的情况。

  • 你可以停下重新一遍又一遍地实现同一个模式,并且将它们抽象为可观察序列的操作。也许这其中最普遍的是:

    • retry
    • combileLatest(将多个多状态对象的最新值结合起来,这和 Excel 或其他电子制表软件在计算公式时做的工作很类似)
    • map(将序列的值转化并放入另一个序列)
    • merge(将多个源的事件结合起来并放入同一个源)
    • flatMapLatest(当下一个计算请求到来时,自动地取消之前的异步操作)
    • refCount(当你想要下载什么东西并且想确认至少有人需要下载结果时,下载就应该继续,但是如果没有人需要下载结果,那么下载就会被自动取消)
    • zip(想要创建 N 个网络请求,等到所有请求都完成了之后映射它们的结果?这当然可以,轻而易举)
  • 你可以统一所有不同的通知并观察其中一个的原理,例如:

    • 代理
    • 通知中心的通知
    • KVO
    • 目标 / 行动的模式
  • 通过使用 Rx 你不仅可以将计算结果连起来,还可以更随意地取消(处理)计算,例如:

    • app 中有三处下载图片的请求,如果其中的两个取消了请求,而依然有人需要那些信息,那么图片还会继续下载
    • 你需要做很多由异步操作连接起来的复杂的数据转换,而它们有可能会失败,一旦使用了结束操作,则所有的东西都会被自动处理。

顺便一提,这需要什么成本吗?

我认为还没有什么显著的性能影响,我们真的实在地以性能为中心,并且我认为性能影响可能比人们认为的更少,比我们对其施加的性能测试更多。

我们有一个小型的包含在项目中的性能基准,我们用其来衡量操作的处理器使用率和它们在使用时的分配值,所以测试每个操作或结构的性能都是很容易的。

恕我直言,最大的成本是人们误用概念 / 库的时候。在那样的情况下,世事难料。那不是概念 / 库自己的问题,但是依然是我会考虑的一种潜在成本。不理解怎么使用概念、或错误地使用概念都是成本最高的。

如果应用没有使用单向数据流作为架构,那么仅仅引入 Rx 而不是适应架构的话也许不会自动地解决任何问题。情况可能还会更糟,因为你现在又多加了一层你不完全理解怎么去使用的架构。另一方面来说,有很多情况下,怎么使用 RxSwift 是很清晰的。例如网络层、数据模型的观察等。所以以有益的方式开始使用 Rx 并没有那么难,你可以看它是如何执行的,然后就能慢慢在其之上构建自己的代码。我们曾经试图纠正这个错误,提供了一些如何使用它的小例子(RxExample 项目),但是看起来我吗依然需要做更多这方面的工作。

人们使用这个库时面临的其中一个现实问题是太依赖调用堆栈调试技术。用这样的方式调试任何 reactive 库都不正确。我们试图通过提供一系列可以用于调试目的和跟踪系统行为的操作来纠正这个错误。

我们还有很多有关 reactive 框架的工作和应用 Rx 到一些具体问题的工作需要完成。

你将怎么描述 RxSwift 的成熟度?你知道有什么项目正在使用它吗?

我个人一直对使用一些新的闪耀的 / 银子弹项目抱有怀疑的态度,所以我也认为人们刚开始会对使用 RxSwift 抱有怀疑的态度,而且我也是这么期望的。

但是从另一个角度来说,我们所有的事情都是在 GitHub 上做的并且是完全开放的。

在开源的应用中,我了解到的最有名的也许是 Artsy 的 Eidolon 应用。它的生态系统正在与 GitHub 上的 RxSwiftCommunity 一起逐步形成。

CocoaPods 给我们提供了下载数据和使用某项 pod 的应用数量。RxSwift ~> 2.0

开始变的十分引人注目,所以我对项目的前景很兴奋。我们还支持 Carthage 和 Linux 上的 Swift 包管理器,但是我不确定用什么方式能够提供更精确的使用数据。

RxSwift 的未来发展中还包括什么?

在 2.0.0 版本中我们没有期待更多的本质性变化。我们也许会加入一些小功能,提升 Linux 支持等。

当我们开始着手于 Swift 3.0 编译器后,我们将会给出更精确的蓝图。总的来说,我们的目标是:

  • 类型更安全
  • 性能更好
  • 功能更少

对,你没听错,我们确实致力于更少的功能,而尽可能将所有的东西都放入我们的整个项目的生态下。我认为现在的 RxSwift 库就是我们所期待的规模,但是保持这个规模则非常具有挑战性。添加新的操作和功能要面临很大压力。

这也意味着说服人们不在新的版本中包括一大堆新操作和扩展是好事肯定是非常困难的,并且我也认为人们也会失望,但是为了增加这个项目的可维护性,这确实是我们需要做的。

我认为保持 RxSwift 的功能将是最难执行的部分。

你在 Swift 中实现 Rx 有什么经验?语言的性能怎么样?

Swift 真的是一个很好的语言,而且它确实是我使用过的最美的语言之一。而另一方面,它还没有什么明显能使它十分有用的特性,但是它还是一种很年轻的语言所以我确信这个问题会被解决。

如果你粗略地看,那么 Swift 看起来像一个 C++ 的简约、现代的版本,但如果你看一下它的生成代码,你会惊讶于它生成代码所需要的执行时间开销。由于它与 Objective

C 的互通性,有些代码是必要的,但我们还是希望这样的问题能被解决。

可以这么说:Swift 编译器依旧是一个学步儿童。我猜测所有的事情都表现得已如期望。

RxSwift 尝试尽可能地与ReactiveX.io和它的文档相一致,它可以通过 CocoaPods、Carthage 或直接通过 GitHub 进行安装

查看英文原文:RxSwift Brings Native Reactive Functional Programming to Swift


感谢张龙对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

移动iOS语言 & 开发