概述
很多小伙伴在初学 RxSwift 时,在面对大量的操作符和各种抽象的概念时可能都会感到无从下手,但其实官方提供的 Example 就能够很方便的帮助我们学习 RxSwift 的各种概念以及如何与 MVVM 相结合。毕竟直接看实际的运用场景比看抽象的概念要容易理解的多。
我个人认为 RxSwift 的核心其实很简单,就是把要操作的事件、属性都抽象成一个个Observable
,剩下的变换、组合、过滤、响应(订阅)、线程调度、生命周期管理都是你对 Observable
的加工和处理。如果你真的理解了什么是 Observable
,哪些东西可以被包装成 Observable
那么你已经成功了一半啦,就像你刚学面向对象编程,搞懂了什么是对象,哪些东西可以被抽象成对象以后,后面的学习是不是顺利了很多呢。
其实初学的时候真的不用在面对大量的操作符时困惑,也不要有怕难的心态,觉得这么多东西我得记到什么时候。可以先看一遍文档有能力的也可以看看源码。用的多了你会发现和我们平常写代码一样,常用的 Api 就那些,其他用的少的真忘记的时候再翻翻文档就好。(中文文档 RxSwift-Chinese-Documentation)
好了废话不多说,本篇所有示例代码在 RxSwift 官方项目中就有,下载完了直接运行即可。
可以看到官方为我们分了三块内容,
- iPhone Example
- TableView 和 CollectionView Example
- 综合的 Example
本篇文章我们先从这些 Demo 里最简单的 Bindings 说起。
Adding numbers
先看运行效果有三个 UITextField
任何一个 UITextField
输入内容时,把三者相加,在最底部 UILabel
显示结果。
这是一个很简单的响应式编程的例子,这个页面主要由四个元素组成
@IBOutlet weak var number1: UITextField!
@IBOutlet weak var number2: UITextField!
@IBOutlet weak var number3: UITextField!
@IBOutlet weak var result: UILabel!
先考虑一下以往我们会怎么实现:
- 监听三个
UITextField
的文本框改变(Delegate
、KVO
、NSNotificationCenter
、addTarget
)。 - 在回调方法中将三个
UITextField
输入内容转成Int
相加并赋值给UILabel
。
原生实现最大的问题在于,无论哪种方式,监听文本框内容改变的代码都不是那么的优雅。
看一下用 RxSwift 如何实现。回想我们刚刚所说的核心,把属性或者事件抽象成 Observable
,再对 Observable
进行操作。
- 包装 (把三个
UITextField
输入的内容抽象成三个Observable
)
number1.rx.text.orEmpty
number2.rx.text.orEmpty
number3.rx.text.orEmpty
- 组合 (把三个
Observable
组合,并将每一个值转成Int
类型相加)。
Observable.combineLatest(number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty) { textValue1, textValue2, textValue3 -> Int in
return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0) + (Int(textValue3) ?? 0)
}
文档中为我们描述的 combineLatest
的作用。
当多个 Observables 中任何一个发出一个元素,就发出一个元素。这个元素是由这些 Observables 中最新的元素,通过一个函数组合起来的。
说的再直白一点就是,这三个 UITextField
任意一个有新值的时候都会被组合成新的Observable
。
- 转换 (把
Int
类型转换成String
类型)
.map { $0.description }
- 响应 (把值绑定到
UILabel
)
.bind(to: result.rx.text)
- 管理生命周期
.disposed(by: disposeBag)
怎么样是不是很简单呢,只需要6行代码我们就完成了这个需求。
SimpleValidation
运行效果
- 当用户输入用户名时,如果用户名不足 5 个字就显示红色提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。
- 同样的当用户输入的密码不到 5 个字时也显示红色提示语。
- 当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。
- 当点击绿色按钮后弹出一个提示框
页面由以下元素组成
/// 用户名
@IBOutlet weak var usernameOutlet: UITextField!
/// 用户名红色提示语
@IBOutlet weak var usernameValidOutlet: UILabel!
/// 密码
@IBOutlet weak var passwordOutlet: UITextField!
/// 密码红色提示语
@IBOutlet weak var passwordValidOutlet: UILabel!
/// 按钮
@IBOutlet weak var doSomethingOutlet: UIButton!
还是先考虑一下以往我们会怎么实现:
- 监听两个
UITextField
的文本框改变(Delegate
、KVO
、NSNotificationCenter
、addTarget
)。 - 在回调方法中判断用户名是否符合要求,用判断结果去设置提示语是否隐藏和密码框是否可以输入。
- 在回调方法中判断密码是否符合要求,用判断结果去设置提示语是否隐藏。
- 拿到前两个判断的结果,当前两个的结果都为真时,按钮可以点击。
- 添加按钮点击事件。
看一下用 RxSwift 如何实现。还是我们刚刚所说的核心,把属性或者事件抽象成 Observable
,再对 Observable
进行操作。
- 用户名格式是否正确 (变换)
let usernameValid = usernameOutlet.rx.text.orEmpty
.map { $0.count >= minimalUsernameLength }
.share(replay: 1)
- 密码格式是否正确 (变换)
let passwordValid = passwordOutlet.rx.text.orEmpty
.map { $0.count >= minimalPasswordLength }
.share(replay: 1)
- 两者都正确(组合 ,
combineLatest
作用上面介绍过,再巩固一下)
let everythingValid = Observable.combineLatest(usernameValid, passwordValid) { $0 && $1 }
.share(replay: 1)
这里你可能比较疑惑这个 .share(replay: 1)
是干嘛的。
通俗一点说,当有多个订阅者去订阅同一个 Observable
的时候,我们不希望 Observable
每次有新的订阅者都去执行。
- 响应
usernameValid
.bind(to: passwordOutlet.rx.isEnabled)
.disposed(by: disposeBag)
usernameValid
.bind(to: usernameValidOutlet.rx.isHidden)
.disposed(by: disposeBag)
passwordValid
.bind(to: passwordValidOutlet.rx.isHidden)
.disposed(by: disposeBag)
everythingValid
.bind(to: doSomethingOutlet.rx.isEnabled)
.disposed(by: disposeBag)
由于 .share(replay: 1)
的存在,这个判断密码或者用户名是否合法的操作,不管以后被多少人订阅,只会判断一次,这样可以避免不必要的资源消耗。
- 按钮添加点击事件
doSomethingOutlet.rx.tap
.subscribe(onNext: { [weak self] _ in self?.showAlert() })
.disposed(by: disposeBag)
当然这步我们是可以通过扩展的方式让他更加优雅的,这篇我们不做过多讲解。
最后说句题外话,学东西还是要从简单的学起,这样会多一些正面积极的反馈,就像是打游戏总会一段时间就有一个小的奖励,刺激你继续玩下去。如果一上来就看大量的操作符,各种概念和源码,挫败感一大就不想学了。
下一篇将更新如何与 MVVM 结合做一个 GitHub
的注册界面。