专家不想让您知道反应式编程的五个不可思议的秘密!

根据 2017年2月24日在墨尔本 Playgrounds 的演讲

我被要求拿出一个超级点击链接标题作为标题,这里是: 专家不想让您知道的反应式编程的五个不可思议的秘密!

(我仍然不清楚为什么专家不希望您知道这些事情。)

我听说它说过关于学习Lisp或Prolog或函数式编程的知识-即使您可能再也不会使用Lisp了,学习语言和它的工作方式(可能)会(改变)您思考代码的方式和设计程序的方式。

同样的事情适用于反应式编程吗? 它为我做了; 用RxSwift编写应用程序突出了一些我已经知道的最佳实践和体系结构选择,但是在以响应式方式进行思考时,它们处于最前沿。

这些实际上并不是本质上的“秘密”,而是更像是“大图构想”,随着我对反应式编程的更多了解,这些构想在我脑海中得以巩固。 继续阅读以了解它们是什么!

1.你已经知道了

好消息是您已经知道构成反应式编程的部分。

如果您以“传统”可可风格编写iOS应用程序,并且了解函数编程,那么您已经知道什么是函数。 它更多地是关于学习一种新的风格,一种新的思维方式以及也许一些新单词,例如每个人都喜欢的“ monad”。

反应式编程也是如此:您将看到以熟悉但新颖的方式使用的旧概念,例如序列,闭包和map 。 它们的组合方式以及用于描述它们的语言可能是新的,但构成要素是相同的。

2.一切都是一个序列

在设计反应式应用程序时,我首先想到的是:序列是什么? 我喜欢将它们分为两大类,即输入和输出。

由于RxCocoa提供了对通用类的扩展,因此您可以直接获得许多这些序列或可观察对象

例如, UISlider具有可观察到的,提供一系列Float值的对象。 UIButton具有一个可观察到的对象,它在每次点击按钮时都会触发。

您也可以使自己的观测值。 我为Mac应用程序编写了一个,以跟踪鼠标单击。 只要在视图上单击鼠标, NSViewController收到mouseDown(with:event)方法调用。 您可以让RxCocoa侦听该方法调用,并将其转换为可观察到的:

 self.rx.sentMessage(#selector(NSResponder.mouseDown(with:))) 
.map({ (params) -> NSPoint? in
if let event = params.first as? NSEvent {
return self.view.convert(event.locationInWindow, from: nil)
}
return nil
})

在这种情况下,第一行设置了一个observable,每次调用mouseDown(with:)都会产生一个值。 然后,我们可以map到该可观察值(记住,这只是一个序列),并生成可观察到的NSPoint值的输出。

3.小逻辑

在上面的示例中,我们有一系列事件需要转换为一系列点。 每个转换都会通过您的应用程序推送数据。

在您的应用程序的大框内,其中包含较小的序列。

前进的每一步都是自己的操作,看起来就像是另一个带有闭合map 。 这种结构意味着您的程序可以由小的,离散的逻辑部分组成,而不是由整体的大规模视图控制器组成。

4.声明式

所有这些序列都在浮动,如何将它们连接在一起以执行有用的工作? 到目前为止,我们只看到序列转换为其他序列。

您可以对可观察对象进行的另一项有用的操作是订阅它们。 这意味着,每当有新值可用时,或发生其他错误(例如错误或可观察到的完成情况)时,您都可以提供闭包来对此做出反应。

在最简单的情况下,假设您有一个可观察的对象,它产生的字符串来自某些来源(例如网络)。 您可以订阅此可观察对象,并设置标签的值。

 someStringObservable .subscribe(onNext: { (stringValue: String) in 
self.label.text = stringValue
})

作为快捷方式,您可以使用bind函数并利用更多的RxCocoa扩展:

 someStringObservable .bindTo(self.label.rx.text) 

magic rx属性提供可以读取或写入的属性。 例如,可以绑定一个文本字段,但是您也可以订阅更改,因为它们接受用户输入。

一旦设置了所有可观察对象,地图,订阅和绑定,魔力就来了:到那时,您可以启动应用程序,并将所有魔力连接在一起。 借助声明性方法,您已经设置了所有转换和关系的逻辑,现在可以等待输入开始通过。

5.可测试的代码

将逻辑分割成小块意味着它们也变得易于测试。

举一个非常简单的例子:一个将可观察值生成整数的类作为输入:

 class SomeController { 
// Input observable providing a sequence of integers
var intObservable: Observable
  init(intObservable: Observable) { 
self.intObservable = intObservable
}
}

它具有计算属性,该属性将输入整数转换为字符串:

 extension SomeController { 
// Output observable that converts the integers to strings
var stringConversionObservable: Observable {
return intObservable .map({ (intValue: Int) -> String in
return String(intValue)
})
}
}

您想测试一下。 在您的应用中, intObservable来自某个地方,可能是网络,也可能是用户输入,这并不重要。 您要测试的只是转换操作。 在这种情况下,转换逻辑很简单,但是您可以想象其中有一个包含更复杂业务逻辑的map

在整数序列和字符串序列之间,我们如何模拟这些东西? 如果只有一些简单的方法可以模拟有序的可迭代整数序列,对吗? 🤔

在这一点上,我希望您正在考虑数组。 是的,您可以使用整数输入数组 ,将其推入可观察对象,然后获取字符串输出数组:

 // Test input data 
let input = [1, 2, 42]
 // Turn the array into an observable 
let inputObservable = Observable.from(input)
 // Initialize the controller with our test observable 
let controller = SomeController(intObservable: inputObservable)

可观察值通常是异步的,因为您无法提前知道何时可以使用某个值。 由于这是使用固定数组进行的测试,因此我们可以将其变为可观察到的阻塞,以便它等待map同步完成。

 // Thanks to the toArray() call, this will be an regular array of strings 
let output = try! controller.stringConversionObservable.toBlocking().toArray()

然后,只需确保输出符合您的期望即可。

 let expectedOutput = ["1", "2", "42"] 
XCTAssert(output == expectedOutput)

做完了! 将输入和输出建模为序列的好处在于,当我们需要在测试中对它们进行模拟时,由于数组的缘故,这样做非常容易。

超越五个秘密

学习了反应式编程的基础知识之后,这些“五个秘诀”囊括了一些有关软件体系结构以及我想如何构造代码的重要思想。 我在考虑数据流和可测试代码的小块方面的思考更多。

如果您想了解有关RxSwift的更多信息,建议您使用以下资源:

  • RxSwift入门页面
  • rx-marin —带有许多RxSwift示例代码的优秀文章
  • ReactiveX.io — Rx风格的编程的语言和平台无关指南

如果您在Playgrounds演讲中,并且想从演讲中查看一些资源,请执行以下操作:

  • 样例游戏
  • 演讲幻灯片