自学Swift有一段时间了,在一个技术群里偶然听到RxSwift的概念,了解了以后,觉得很有必要学一学。但是开始接触真的比较难理解。从网上找了一些资料以后开始了RxSwift之旅。
主要资料参考如下(感谢分享的力量):
http://www.codertian.com/2016/12/10/RxSwift-shi-zhan-jie-du-base-demo/
http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/
博主写的很好,所以没必要再照着写一遍。这边文章主要针对上面的博客写一些自己的理解以及总结吧。
RxSwift实战这篇文章中,是基于MVVM写的。其中用到的文件有Service,ViewModel,Protocol,ViewController(页面比较简单,并未涉及到View。)
Service主要负责一些网络请求和一些数据的访问操作,供ViewModel使用。
注册页面三个文本框加一个按钮,要实现的效果如下。
用户名和密码都有一定格式要求。
先把整体的流程贴在这里。
1.将用户名的TextField流做为Observable(被观察者)绑定到ViewModel的userName。
其中userName声明为
let username = Variable<String>("") //初始值为""
Variable是BehaviorSubject一个包装箱,就像是一个箱子一样,使用的时候需要调用asObservable()拆箱,里面的value是一个BehaviorSubject,他不会发出error事件,但是会自动发出completed事件。
Variable和BehaviorSubject都是Subjects概念的范畴,具体可参考http://www.codertian.com/2016/11/27/RxSwift-ru-keng-ji-read-document/。此处理解为把userName声明为一个Observable即可。
2.将TextFeld的text绑定到userName上
usernameTextField.rx.text.orEmpty
.bindTo(viewModel.username)
.addDisposableTo(disposeBag)
// usernameTextField.rx.text.orEmpty是RxCocoa库中概念,把TextFiled的text变成了一个Observable,orEmpty把String?过滤nil
// 变为String类型。
- 在vewModel中处理userName
let usernameUsable: Observable<Result> // 这个是对userName处理以后的输出output
// 如果只是检测格式,那么可以写成如下形式
usernameUsable = userName.asObservable().map.({(userName) -> Result in
if userName.characters.count == 0 {
return .empty
}
if userName.characters.count < characterCount {
return failed(message:"长度至少6个字符")
}
return just(.ok(message:"用户名可用")
}).shareReplay(1)
// map函数是通过传入一个函数闭包把原来的sequence转变为一个新的sequence的操作
如果这其中涉及到了网络请求,或者是耗时的读取数据库操作,那么我们会用到flatMap函数
flatMap将一个sequence转换为一个sequences,当你接收一个sequence的事件,你还想接收其他sequence发出的事件的话可以使用flatMap,她会将每一个sequence事件进行处理以后,然后再以一个新的sequence形式发出事件。
具体参考:
http://www.codertian.com/2016/12/01/RxSwift-ru-keng-ji-learn-the-difficulty/
如果我们要检测用户名是否已经存在,需要去发送网络请求或者去查询本地数据库,那么就要用flatMap
usernameUsable = userName.asObservable().flatMap({(userName) in
return service.validateUsername(username)
.observeOn(MainScheduler.instance)
.catchErrorJustReturn(.failed(message: "username检测出错")
})
要注意的是
Map 中的闭包返回的是一个经过闭包参数userName(这也是原始序列的元素)处理过后的Result类型的元素。
flatMap中的闭包返回的是一个 Observable<Result> 序列,例如
service中validateUsername中返回的 .just(.failed(message: "号码长度至少6个字符"))
service中validateUsername方法如下
func validateUsername(_ username: String) -> Observable<Result> {
if username.characters.count == 0 {//当字符等于0的时候什么都不做
return .just(.empty)
}
if username.characters.count < minCharactersCount {//当字符小于6的时候返回failed
return .just(.failed(message: "号码长度至少6个字符"))
}
if usernameValid(username) {//检测本地数据库中是否已经存在这个名字
return .just(.failed(message: "账户已存在"))
}
return .just(.ok(message: "用户名可用"))
}
观察map和flatMap函数定义
public func map<R>(_ transform: @escaping (Self.E) throws -> R) -> RxSwift.Observable<R>
public func flatMap<O : ObservableConvertibleType>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E>
其实这里的map和flatMap的作用是一样的。map函数可以对原有序列里面的事件元素进行改造,返回的还是原来的序列。而flatMap对原有序列中的元素进行改造和处理,每一个元素返回一个新的sequence,然后把每一个元素对应的sequence合并为一个新的sequence序列。
观察fatMap,声明了一个遵循ObservableConvertibleType协议的泛型类型O(可以理解为一个序列)。闭包中的参数是原始序列的元素(在这指的是userName:String),闭包返回的是遵循ObservableConvertibleType协议的泛型类型O,也就是说返回的是一个序列。最后函数返回的是O类型的元素组成的一个序列:Observable<O.E>,在这里,O.E指的是Result类型,也就是说flatMap函数最后返回的是O类型的。
(有理解的不对的地方,欢迎指正)。
viewModel和Service中工作完成以后,可以在controller写后续工作了
思考?如何让userNameUsable绑定到提示用户名是否可用的Label呢?
当然可以用subscribe(onNext:)方法
viewModel.userNameUsable.subscribe(onNext: { [weak self] (result) in
switch result {
case .ok(let message):
self!.nameTipLabel.text = message
case .empty:
self!.nameTipLabel.text = ""
case .failed(let message):
self!.nameTipLabel.text = message
}
}).addDisposableTo(disposeBag)
但是这样写有一些繁琐,毕竟这些放在viewController里面写不太优雅,如果要在Result不同情况下显示不同颜色,那么代码量又会进一步增多。
想要解决这个,就需要用到UIBindingObserver了,这个是个很有用的东西。
我们现在为UILabel自定义一个validationResult
extension Reactive where Base: UILabel {
var validationResult: UIBindingObserver<Base, Result> {
return UIBindingObserver(UIElement: base) { label, result in
label.textColor = 根据result处理
label.text = 根据result处理
}
}
}
// 自定义了一个Observer,对UIlabel进行了扩展,根据result结果,进行他的text和textColor的显示
详细内容见http://www.codertian.com/2016/12/01/RxSwift-ru-keng-ji-learn-the-difficulty/
然后viewController中可以写成如下形式,优雅了很多吧!
viewModel.userNameUsable
.bind(to: nameTipLabel.rx.validationResult)
.addDisposableTo(disposeBag)
RxSwift中除了Observable,还有一个概念是Driver
下面来看一下Driver
其实Driver和Observable的使用结构是一样的只是Driver和Observable有点区别,Driver是RxSwift专门针对UI操作,而Observable是一个通用的东西
下面是登录页面的Driver使用
页面效果如下
同样先写Service,这里如果要校验一下userName是否是已经存在的话,那么要用flatMap,因为要有耗时操作,如果只是校验一下格式的话,那么要用map。
假设这里需要校验用户名是否已存在。那么
在viewModel中声明输入如下:
// input
let userName = Observable<String>("")
// output
let userNameUsable: Driver<Result>
ViewModel的init方法中初始化userNameUsable
userNameUsable = userName.asObservable()
.asDriver(onErrorJustReturn: "")
.flatMapLatest { username in
return service.loginUserNameValid(username)
.asDriver(onErrorJustReturn:
.failed(message: "连接server失败"))
}
在ViewController里面进行绑定
viewModel.userNameUsable.drive(nameTipLabel.rx.validationResult)
.addDisposableTo(disposeBag)
如果没有自定义Observer,那么写法如下:
viewModel.userNameUsable.drive(onNext: { [weak self] (result) in
switch result {
case let .ok(message):
// 处理
case .empty:
// 处理
case let .failed(message):
// 处理
}).addDisposableTo(disposeBag)
注意:只有Driver可以使用drive方法,Observable是不能使用的。
另外官方的写法是这样的:
viewModel初始化方法如下
init(input: (userName: Driver<String>, password: Driver<String>, loginTaps: Driver<Void>), service: ValidateService)
// init的参数使用一个元组
viewController里面
let viewModel = LoginViewModel(input: (userName: nameTextField.rx.text.orEmpty.asDriver(), password: passWordTextField.rx.text.orEmpty.asDriver(), loginTaps:doSomething.rx.tap.asDriver() ), service: ValidateService.instance)
viewModel.userNameUsable.drive(nameTipLabel.rx.validationResult)
.addDisposableTo(disposeBag)
还有剩下的列表页,使用流程和上述方式并无二致。可参考博主原文。
总之还是要多写。否则看过几天之后便全忘记了。
最后感谢原文博主分享。