一同养成写作习气!这是我参与「日新方案 4 月更文挑战」的第27天,点击检查活动概况。

  • 本文主要介绍RxSwiftmvvm的运用,这里以登录为例,界面如下:

RxSwift学习-24-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的事情转化为可调查序列

RxSwift学习-24-RxSwift中MVVM的使用


  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的代码少了很多,便是咱们进行展现咱们的绑定关系

RxSwift学习-24-RxSwift中MVVM的使用

4. 总结

把咱们的界面的UI元素转化为可调查序列,viewmodelInput。之后经过咱们的逻辑处理viewmodel输出咱们的Output。经过咱们的序列进行衔接咱们UI操作作为输入源element,经过订阅对咱们的UI进行展现和操作