Via 由 [ ] 提供

Swift 响应式编程

译者:李鑫

原文:REACTIVE PROGRAMMING IN SWIFT

本文为极客学院Wiki组织翻译,转载请注明出处。

时间:2016.3.4

本文将介绍一个响应式编程架构 RxSwift,并结合使用 Swift 的函数式功能来编写更简洁、更表现力的代码,从而管理应用状态及并行任务。

Swift 及其函数式功能

Swift 可被认为是一种现代的面向对象语言,对泛型编程有着原生支持。虽然它不是一种函数式语言,但其中的一些特性却可以让我们利用函数式方式来编程,比如可以利用闭包first-class 类型函数,以及不可变的value 类型

然而,Cocoa Touch 是一个面向对象的架构,有着这一范式所强制的约束。软件开发中常见的问题在于如何管理共享应用状态以及异步数据的并行任务。

函数式编程解决这些问题的办法是,赋予不可变状态一定的特权,以及将应用逻辑定义为不会在应用生存周期内改变的表达式。通过定义自包含的函数,并行化计算就会变得简单,最大程度减少并发问题。

响应式模型

响应式编程根源于 FRP(函数响应式编程)命令驱动的编程方式,是以异步数据流的形式进行编程。

这可能有些难懂,所以最好通过一个简单的例子来大体了解一下。

表达一个变量间关系

假如有 2 个变量(A 和 B),它们的值会在应用运行时中经常改变。还有一个变量(C),它的值取决于前两个变量值。

2. var B = 20
3. let C = A * 2 + B
4. 
5. // 当前值
6. // A = 10, B = 20, C = 40
7. 
8. A = 0
9.
10. // 当前值
11. // A = 0, B = 20, C = 40  

C 值与 A 和 B 有关,B 只被当 A 和 B 的赋值操作执行后,它们三者之间的关系很快就解散了。这时再改变 A 与 B 的值,将不会对 C 的值有任何影响。

所以,在任何指定时间,要想计算表达式,就必须根据 A 和 B 的当前值,重新指定 C 值,重新计算。

用响应式编程方式该怎么做呢?

采用响应式模式,我们将创建两个流,来传递 A 或 B 值的改变。

一般可使用弹珠图来展示这个原理。如下图所示,每一行表示连续的一段时间,每一个弹珠表示发生在特定时刻的一个事件。

弹珠图

Cocoa Touch 中的做法

在 Cocoa Touch 中,使用键值对观察,为发生改变的变量添加观察者,当 KVO 系统通知时再进行处理。

self.addObserver(self, forKeyPath:"valueA", options: .New, context: nil)
self.addObserver(self, forKeyPath:"valueB", options: .New, context: nil)

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    let C = valueA * 2 + valueB
}

如果变量与用户界面相连,那么可以在 UIKit 中定义一个当触发变化事件时即被调用的处理器:

sliderA.addTarget(self, action: "update", forControlEvents: UIControlEvents.ValueChanged)
sliderB.addTarget(self, action: "update", forControlEvents: UIControlEvents.ValueChanged)

func update() {
    let C = sliderA.value * 2 + sliderB.value
}  

但是,对于调用的变量、它们的生存周期以及改变它们值的事件,以上两种方法都没有定义一种持久、显式的关系。

我们可以用响应式编程模式来处理这种情况。当前对于 OS X 和 iOS 开发者而言,有多种不同的实现,比如 RxSwiftReactiveCocoa

下面简单介绍一下 RxSwift,不过这两种架构的概念是相似的。

RxSwift

RxSwift 继承自观察者模式,模拟 Cocoa Touch 对象中的异步数据流,按通常的集合来看待这些对象。通过利用可观测流继承一些 Cocoa Touch 类,可以订阅它们的输出,并利用复合运算(如 filter()merge()map()reduce() 等)来使用这些输出。

还回到刚才的例子中,假设一个 iOS 应用有两个滑块(sliderA 和 sliderB),并希望利用之前的表达式(A * 2 + B)不断更新标签(labelC)的值:

1. combineLatest(sliderA.rx_value, sliderB.rx_value) {
2.      $0 * 2 + $1
3. }.map {
4.      "Sum of slider values is \($0)"
5.  }.bindTo(labelC.rx_text)

利用 UISlider 类的 rx_value 后缀,将滑块的值属性转化为可观测类型,
通过在每个滑块的可观测类型上使用 combineLatest() 操作,我们还创建了一种新的可观测类型,只要其中任何一个源流释放出一个项目,它就会释放项目。结果就是一个元组,每个滑块值都可以通过操作回调而转换(代码行 2)。然后将变换值映射到信息性字符串(代码行 4),并将其值绑定到标签上(代码行 5)。

通过组合 3 个独立的操作(combineLatest()map()bindTo()),我们就能精确地表达三种对象之间的关系并不断更新应用的 UI,响应应用状态中的改变。

Sum_of_slider_values

额外介绍

上面的内容只是对 RxSwift 用途做了一个粗浅的介绍。

参看样例代码,在这个例子中,使用可链接的异步任务下载在线资源。如果这篇文章引发了你的好奇心,一定要看看这个例子。

然后,可以读读这篇文档,学习其他一些 API 扩展,采用一种函数式并具有表现力的方式来开发 iOS 应用。

还可阅读 使用轻量级模式 来了解Swift 模式如何帮助你处理大量相似对象。

作者简介

Milton Moura(@mgcm)是一位葡萄牙的自由 iOS 开发者。他曾就职于涉及航空、电信、能源等领域的多家公司,如今全心致力于使用苹果技术开发优秀应用。除了醉心于设计与用户交互外,他还非常喜欢新的软件开发方式。其博客为:http://defaultbreak.com