专家不想让您知道反应式编程的五个不可思议的秘密!
根据 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演讲中,并且想从演讲中查看一些资源,请执行以下操作:
- 样例游戏
- 演讲幻灯片