一同养成写作习气!这是我参与「日新方案 4 月更文挑战」的第27天,点击检查活动概况。
- 本文主要介绍
RxSwift
中mvvm
的运用,这里以登录为例,界面如下:
这里咱们的viewmodel
的思路便是输入
和输出
的操作,咱们的逻辑操作
都在ViewModel中
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform(input:Input) -> Output
}
class ViewModel: NSObject {
let disposeBag = DisposeBag()
}
咱们定义一个协议,这里相关一个Input和Output
类,以及一个transform
办法,和咱们RXSwift
基类相同,明确基类的效果。
1. UI界面元素操作绑定
-
input
咱们看下咱们的输入,对于上面的界面主要是4个输入
/// 输入信号
struct Input {
let iphoneNumber: Driver<String>
let codeTextNumber: Driver<String>
let codeSenderTap: Driver<Void>
let loginButtonTap: Driver<Void>
}
-
Output
输入的话,咱们相同划分为4个
struct Output {
/// 发送按钮状况
let codeSenderEnable: Driver<Bool>
/// 登录按钮状况
let loginButtonEnable: Driver<Bool>
/// 登录完结跳转
let loginButtonComplete: Driver<Bool>
/// 接口状况
let signingIn: Driver<Bool>
}
咱们看下把咱们的UI事情
转化为咱们的Driver
序列
guard let viewModel = self.viewModel as? LoginViewModel else { return }
let input = LoginViewModel.Input(
iphoneNumber: iphoneTextField.rx.text.orEmpty.asDriver(),
codeTextNumber: codeTextField.rx.text.orEmpty.asDriver(),
codeSenderTap: codeSenderButton.rx.tap.asDriver(),
loginButtonTap: loginButton.rx.tap.asDriver())
这里咱们经过rx把咱们的输入框和按钮转化为咱们的driver
序列,Driver
咱们之前说过主要是对于UI的事情转化为可调查序列
lazy var phoneSignal: Observable<Bool> = {
iphoneTextField.rx
.text
.orEmpty
.map { Validator.phone($0).isValid }
}()
咱们也能够自己进行转化判别输入的手机号是否可用,持续看下转化操作
2. 逻辑处理
- 输入框转化
let codeSenderEnable = input.iphoneNumber.map { num -> Bool in
return num.count >= 11
}
把咱们的输入string ,判别数字是否大于等于11.转化为bool值
- 验证码处理
let iphoneNumberEnable = input.codeTextNumber.map { num -> Bool in
return num.count >= 6
}
相同的验证码输入框进行6位数的判别
- 验证码点击处理
input.codeSenderTap.withLatestFrom(input.iphoneNumber)
.drive { [weak self] num in
guard let self = self else { return }
self.getCode(iphone: num)
} .disposed(by: disposeBag)
这里把咱们的输入框序列源
和验证码点击事情
兼并到一同,merges兼并
。经过运用第二个序列中最新的元素,将两个可调查序列兼并为一个可调查序列
,当咱们订阅的时分会拿到最新的元素
也便是咱们的手机号。这里drive
相似咱们的订阅获取验证码
func getCode(iphone: String) {
request(LoginApi.sms(phone: iphone), Bool.self)
.subscribe { isOk in
NSLog("发送成功\(isOk)")
}.disposed(by: disposeBag)
}
- 登录按钮的处理
let loginButtonEnable = Driver.combineLatest(codeSenderEnable, iphoneNumberEnable).map { num in
return num.0 && num.1
}
经过咱们的combineLatest
,只会保存后发送的信号,并且组合
信号中,只要都发送了才会到共同订阅的.因而只要咱们的验证码和输入框都契合我的要求才能够点击。
之后获取咱们手机输入框和验证码输入框的值
let codeAndIphone = Driver.combineLatest(input.iphoneNumber, input.codeTextNumber) {($0,$1)}
处理咱们登录按钮的事情
let activity = ActivityIndicator()
let signingIn = activity.asDriver()
let loginButtonComplete = input.loginButtonTap
.withLatestFrom(codeAndIphone)
.flatMap { [weak self] num -> Driver<Bool> in
guard let self = self else { return Driver.just(false) }
return self.logon(iphone: num.0, code: num.1, activity: activity)
ActivityIndicator
是相似咱们loading
的加载,咱们把咱们点击和咱们的输入框的值兼并,之后进行转化
flatMap
,由于咱们的codeAndIphone
兼并的是一个元祖类
型的元素,咱们经过flatMap
转化为一个可调查序列,相当于把2个序列源转化为一个
。
- 登录请求
func logon(iphone: String, code: String, activity: ActivityIndicator) -> Driver<Bool> {
return request(LoginApi.smsVerify(phone:iphone, code:code ), Token.self)
.trackActivity(activity)
.map { token -> Bool in
AuthManager.setToken(token: token)
return true }
.asDriver(onErrorJustReturn: false)
}
咱们的请求是队moya进行封装,你也能够运用RxAlamofire
private func rawRequest<T: Decodable>(_ target: TargetType, _ type: T.Type) -> Observable<Pandora<T>>{
NetworkManager.provider.rx
.request(MultiTarget(target))
.subscribeOn(CurrentThreadScheduler.instance)
.asObservable()
.map(Pandora<T>.self ,using: CleanJSONDecoder())
.catchError { error in
if let moyaError = error as? MoyaError {
throw ApiError(statusCode: moyaError.response?.statusCode ?? -1, code: nil, message: moyaError.errorDescription ?? "")
}
throw ApiError(statusCode: -1, code: .unknwon, message: nil)
}
.share()
.observeOn(MainScheduler.instance)
.filter { pandora in
if pandora.isInvalidToken {
NotificationCenter.default.post(Notification(name: .InvalidToken)) // token 失效
return false
}
return true
}
}
这里 便是把登录请求成果转化为bool
值,中间会保存token
等操作。
3. 输出的绑定
咱们经过transform
处理了咱们的逻辑,接下来便是把咱们的输出的信号绑定到UI上
。
- 验证码按钮
output.codeSenderEnable
.drive(codeSenderButton.rx.isEnabled)
.disposed(by: disposeBag)
咱们的验证码按钮是否可点击绑定咱们的验证码按钮。
- 登录按钮
output.loginButtonEnable
.drive(loginButton.rx.isEnabled)
.disposed(by: disposeBag)
登录按钮的绑定是否可用
- 登录成果
output.loginButtonComplete.filter { $0 }
.drive { [weak self ] _ in
guard let self = self else { return }
self.gotoTabbarViewController()
}.disposed(by: disposeBag)
咱们登录的成果拿到成果后进行调整等操作。
- 正在登录
output.signingIn
.drive { self.view.isLoading(isStop:$0) }
.disposed(by: disposeBag)
咱们视图展现为咱们基类的loading
,是否展现。
func isLoading(isStop: Bool) {
isStop ? startLoading() : stopLoading()
}
以上便是咱们输入和输出,界面的绑定,能够发现咱们vc的代码少了很多,便是咱们进行展现咱们的绑定关系
4. 总结
把咱们的界面的UI元素转化为可调查序列,viewmodel
的Input
。之后经过咱们的逻辑处理viewmodel
输出咱们的Output
。经过咱们的序列进行衔接咱们UI操作
作为输入源element
,经过订阅
对咱们的UI进行展现和操作
。