Functional responsive programming is clear and concise, easy to read, easy to maintain code, let’s look at RxSwift’s best partner: MVVM. Functional Responsive Programming can be seen in two other articles: RxSwift Learning: Functional responsive Programming ideas, RxSwift learning: Functional Responsive programming introduction

  • Download information: Download address

MVVM

The usual architecture: MVC

  • Model: Data layer. Responsible for reading and writing data, saving App state, etc
  • Controller: business logic layer. Responsible for business logic, event response, data processing, etc

Disadvantages:

  • The ViewController plays the role of both View and ViewController
  • And the Model in the VIewController can interact directly with the View
  • When App interactions become complex, viewControllers become bloated, with a lot of code added to the controller, overloading the controller.
MVVM

Advantages:

  • You can slim down the ViewController
  • Reuse of logical views. For example, a ViewModel can be bound to different views, allowing multiple views to reuse the same View logic.
  • Using MVVM can greatly reduce the coupling of code, facilitate unit testing and maintenance, and facilitate collaborative development (for example, one person is responsible for logic implementation, one person is responsible for UI implementation).

Disadvantages:

  • Using MVVM slightly increases the amount of code compared to MVC, but overall reduces the complexity of the code.
  • There is also a learning cost (how to bind data, etc.).

MVVM

MVVM is short for model-view-viewModel. If you’re already familiar with MVC, getting started with MVVM is pretty easy.


MVC is short for Model-view-Controller. MVC has three main layers:

  • Model data layer, read and write data, save App state
  • View page layer, and user interaction, display pages to users, feedback user behavior
  • The ViewController logic layer, updates the data, or pages, handles the business logic

MVC can help you separate data, pages, and logical code. Make each layer relatively independent. This allows you to take some of the reusable functionality out of the way and simplify it. However, once the interaction of your App gets complicated, you’ll find that the ViewController becomes bloated. A lot of code is added to the controller, overloading the controller. At this point, you need to find a way to further isolate the code in your controller and relayer your APP. MVVM is an advanced layered scheme.


MVVM

MVVM and MVC are familiar. But he layered it in more detail:

  • Model data layer, read and write data, save App state
  • View page layer, which provides user input behavior and displays output status
  • ViewModel logic layer, which translates user input behavior into output state
  • The ViewController is responsible for data binding

Yes, the ViewModel is now the logical layer, and the controller is only responsible for data binding. This takes a lot of the burden off the controller. And the ViewModel is independent of the controller and the page. Then you can use it across platforms. You can also easily test it.


The sample

Here we will use MVVM to refactor input validation.

Refactoring:

class SimpleValidationViewController : ViewController {

    ...

    override func viewDidLoad() {
        super.viewDidLoad()

        ...

        let usernameValid = usernameOutlet.rx.text.orEmpty
            .map { $0.characters.count >= minimalUsernameLength }
            .share(replay: 1)

        let passwordValid = passwordOutlet.rx.text.orEmpty
            .map { $0.characters.count >= minimalPasswordLength }
            .share(replay: 1)

        let everythingValid = Observable.combineLatest(
              usernameValid,
              passwordValid
            ) { $0 && $1 }
            .share(replay: 1)

        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)

        doSomethingOutlet.rx.tap
            .subscribe(onNext: { [weak self] in self?.showAlert() })
            .disposed(by: disposeBag)
    }

    ...

}

Copy the code

ViewModel

ViewModel converts user input behavior into output state:

The class SimpleValidationViewModel {/ / output let usernameValid: observables < Bool > let passwordValid: Observable<Bool> let everythingValid: Observable<Bool> init(username: Observable<String>, password: Observable<String> ) { usernameValid = username .map { $0.characters.count >= minimalUsernameLength } .share(replay: 1) passwordValid = password .map { $0.characters.count >= minimalPasswordLength } .share(replay: 1) everythingValid = Observable.combineLatest(usernameValid, passwordValid) { $0 && $1 } .share(replay: 1) } }Copy the code

Input:

  • Username indicates the entered username
  • Password Indicates the password

Output:

  • UsernameValid Specifies whether the username is valid
  • PasswordValid Whether the password is valid
  • EverythingValid Whether all input is valid

Inside the init method, input is converted to output.


ViewController

ViewController is responsible for data binding:

class SimpleValidationViewController : ViewController { ... private var viewModel: SimpleValidationViewModel! override func viewDidLoad() { super.viewDidLoad() ... viewModel = SimpleValidationViewModel( username: usernameOutlet.rx.text.orEmpty.asObservable(), password: passwordOutlet.rx.text.orEmpty.asObservable() ) viewModel.usernameValid .bind(to: passwordOutlet.rx.isEnabled) .disposed(by: disposeBag) viewModel.usernameValid .bind(to: usernameValidOutlet.rx.isHidden) .disposed(by: disposeBag) viewModel.passwordValid .bind(to: passwordValidOutlet.rx.isHidden) .disposed(by: disposeBag) viewModel.everythingValid .bind(to: doSomethingOutlet.rx.isEnabled) .disposed(by: disposeBag) doSomethingOutlet.rx.tap .subscribe(onNext: { [weak self] in self? .showAlert() }) .disposed(by: disposeBag) } ... }Copy the code

Input:

  • Username passes the entered username to the ViewModel
  • Password passes the entered password into the ViewModel

Output:

  • UsernameValid controls whether the prompt is hidden and the password field is available by checking whether the username is valid
  • PasswordValid Specifies whether the password is valid to control whether the prompt is hidden
  • EverythingValid controls whether the button is clickable using whether both are valid

As your App’s interactions get more complex, you can still keep the controller structure clean. This greatly improves the readability of the code. The code will be much easier to maintain in the future.


The sample

Note ⚠️ : THE MVVM described here is not strictly MVVM. But we usually call it MVVM, and it works very well with RxSwift. To learn what an MVVM is in its proper sense, refer to Microsoft’s The MVVM Pattern. Since the | address

  • Download information: Download address