需求
在本文开始前,我们先来理一理一个正常的注册流程中可能会有哪些需求:
- 用户名或密码是否为空
- 用户名密码是否合法
- 重复密码是否一致
- 点击注册发送网络请求
- 处理返回结果
- …….
在这里其实可以把很多处理归并到被观察者 - 订阅者
模式,通过某种事件触发某种行为,某种行为依赖不同事件的状态,所以我们可以通过RxSwift
很方便的去解决我们的问题。
界面设计
新建项目RxSwiftRegister
,pod
引入RxSwift
和RxCocoa
。先来设计一个简单的界面,界面上会有账号和密码的输入框,注册按钮,以及提示信息,然后绑定到LoginViewController
。
验证为空
这里需要验证用户名是不是为空,是否已经注册,密码是否为空,重复密码是否一致。
为了让逻辑与视图分离,这里我们使用MVVM
模式,如果你还不知道MVVM
可以自己先了解一下。
新建LoginViewModel
文件,接受账号、密码、重复密码作为被观察者,然后对其中的text
进行验证处理,返回一个验证的结果(暂时用Bool表示)。
文件目录:
LoginViewModel
编写如下代码:
1 | class RegisterViewModel { |
这里只是简单验证是否为空,后面再改进。
RegisterViewController
代码如下:
1 | class RegisterViewController: UIViewController { |
订阅账号、密码、重复密码是否验证成功的值打印出来,运行可以得到验证,一开始都是false,输入了后变成true。
1 | username is false |
接下来来看一些问题,当有两个订阅者去订阅用户名是否验证ok,然后在验证的时候去打印一下。
1 | viewModel.validatedUsername.subscribe( |
会发现,多了一个订阅者后,验证的逻辑会执行两次,这并不是我们想要的效果,当验证是一个网络请求的话,会发出两个一样的。同一个值只需要验证一次,然后告诉所有的订阅者就行了。这里需要使用到shareReplay(1)
,保证多个订阅者共享单个订阅,并重播最新的一次replay
。
1 | validatedUsername = input.username.map{ |
同样对密码和重复密码也只需要共享一次。
绑定错误到Label
上面只是通过控制台打印了验证的对或错,但是并不知道错的原因,也没有显示到label
上,现在来实现这个效果,我们要定义个表示不同验证结果和信息的枚举。
1 | enum ValidationResult { |
然后修改返回结果类型为ValidationResult
:
1 | enum ValidationResult { |
然后绑定错误到label
,为了让ValidationResult
能绑定到label
,需要给出不同结果的文字颜色和文字信息,这时需要给ValidationResult
扩展一下。
1 | extension ValidationResult: CustomStringConvertible { |
同样为了使label
能够根据对应的信息和颜色更新,需要提供:
1 | extension Reactive where Base: UILabel { |
然后绑定:
1 | viewModel.validatedUsername |
效果如下:
注册按钮状态
接下来需要根据上面的验证结果来确定注册按钮的可点击状态,只有当账号、密码、重复密码都验证通过之后才会变成可点击的状态。
1 | let registerEnabled: Observable<Bool> |
这里有几点:
- 只需要共享一次,使用
shareReplay
- 不用每次改变都发射给订阅者,只有当发生改变时再发射,使用
distinctUntilChanged
- 捕获
self
的弱引用,然后再里面转成强引用
网络验证
前面只是在本地做了一个简单的验证,现在想要验证输入的账号是否能注册,就需要发送网络请求去验证。
这里参考官方给出的例子,通过url判断是否有效:
1 | class GitHubAPI{ |
如果url存在就认为已注册,否则就是没有。
那么用户名验证可以改为:
1 | //flatMapLatest 如果有新的值发射出来,则会取消原来发出的网络请求 |
同样把密码和重复密码也改下。
注册请求
这里和官方例子一样,模拟下注册过程。
1 | func register(_ username: String, password: String) -> Observable<Bool>{ |
然后绑定注册的点击事件,执行注册请求。
1 | //合并注册点击和账号密码序列,每次注册点击,从第二个序列取最新的值 |
trackActivity
是官方例子里面的,用于监控序列的计算中和结束。
到此这个例子就结束了,如图:
项目优化
有几点交互需要优化一下:
- 点击背景收起键盘
- 点击键盘的
Next
调到下一个UITextField
- 点击
Go
触发注册流程
1 | //点击背景收起键盘 |
1 | username.rx.controlEvent(.editingDidEnd) |
完整源码见Github