RxSwift + MVVM: Cách viết một ViewModel đơn giản!

01 June 2019 — Written by Chính Phạm
#RxSwift#MVVM#iOS

Mở đầu

Sau hơn một năm chinh chiến trên dự án thật, dần dần mình áp dụng RxSwift vào nhiều hơn trong việc code dựa trên mô hình Model-View-ViewModel (MVVM).

Việc sử dụng RxSwift + MVVM có thể giúp bạn đi theo hướng đúng đắn và đôi khi có những pha xử lý max ping =))), tin mình đi, ảo diệu vô cùng 😂.

ViewModels

Về cơ bản thì ViewModel trong mô hình MVVM mình ví nó như một cái hộp đen vậy, sẽ có đầu vào (Inputs) và đầu ra (Outputs).

Ở hình trên thì mình có thể hiểu là:

  • Từ view, người dùng nhập vào name là Tèo, sau đó view sẽ gửi cho ViewModel giá trị là input kèm theo yêu cầu là xuất cho tao một đoạn text để hiển thị.
  • ViewModel lúc này sẽ nhận được một yêu cầu kièm input là name, sau đó sẽ xử lý logic và return cho view (output) đoạn text là: Xin chào Tèo!

Dựa theo ví dụ trên thì bây giờ mình bắt đầu tạo một Protocol:

ViewModelType

protocol ViewModelType {
	associatedtype Input
	associatedtype Output

	func transform(input: Input) -> Output
}

Ở đây các bạn có thể thấy, trong protocol ViewModelType mình có khai báo hai associatedtypeInputOutput, bây giờ bất cứ class hay struct nào muốn kế thừa lại ViewModelType thì phải định nghĩa kiểu dữ liệu cho Input và Output một cách ngầm hoặc rõ ràng.

Như mình đã nói đầu bài viết, cái hộp đen (ViewModel) này cần dữ liệu đầu vào (Input) và xử lý cho đầu ra (Output). Vì thế, func transform sẽ đảm nhiệm việc chuyển đổi, xử lý logic của dữ liệu và cho ra kết quả mà ta mong muốn.

Demo

Xem demo trên thì phần nào giúp bạn hình dung bước tiếp theo mình sẽ làm gì rồi chứ 😅

Bắt đầu

Việc đầu tiên là khởi tạo một project, ví dụ cấu trúc thư mục của mình như sau:

- SayHello
|
| - Resources
|	|
|	| - Support
|	|	|
|	|	` - Info.plist
|	|
|	` - Assets.xcassets
|	
` - Views
	|
	| - AppDelegate.swift
	| - Base
	|	|
	|	` ViewModelType.swift
	|
	` - Controllers
		|
		` Home
			|
			| - HomeViewController.swift
			| - HomeViewController.xib
			` - HomeViewModel.swift

Ở demo này thì chúng ta sẽ có một app chứa textfield để nhập vào Tên và gán dòng chử Xin chào TÊN cho label.

HomeViewModel.swift

import Foundation
import RxCocoa
import RxSwift

final class HomeViewModel: ViewModelType {

    // MARK: - Struct

    struct Input {
        let name: Observable<String>
        let validate: Observable<Void>
    }

    struct Output {
        let greeting: Driver<String>
    }

    // MARK: - Public Functions

    func transform(input: Input) -> Output {
        let greeting = input.validate
            .withLatestFrom(input.name)
            .map { return "Xin chào " + $0 + ($0.isEmpty ? "😱" : " 😂") }
            .startWith("")
            .asDriver(onErrorJustReturn: ": 😱")
        
        return Output(greeting: greeting)
    }
}

func transform được hiểu như sau:

Khi người dùng nhấn vào nút Say Hello ở HomeViewController thì ở đây sẽ lấy giá trị truyền vào để tạo ra chuỗi Xin Chào TÊN.

Tiếp theo chúng ta kéo textfield, label và button tương ứng ở HomeViewController.xib sau đó kéo IBOutlet vào HomeViewController.swift

HomeViewController.swift

import UIKit
import RxCocoa
import RxSwift

final class HomeViewController: UIViewController {

    // MARK: - IBOutlets

    @IBOutlet private weak var inputTextField: UITextField!
    @IBOutlet private weak var sayHelloButton: UIButton!
    @IBOutlet private weak var outputLabel: UILabel!

    // MARK: - Properties

    private let viewModel = HomeViewModel()
    private let disposeBag = DisposeBag()

    // MARK: - Override

    override func viewDidLoad() {
        super.viewDidLoad()
        bindViewModel()
    }

    // MARK: - Private Functions

    private func bindViewModel() {
        let inputs = HomeViewModel.Input(
            name: inputTextField.rx.text.orEmpty.asObservable(),
            validate: sayHelloButton.rx.tap.asObservable())

        // Outputs
        viewModel.transform(input: inputs)
            .greeting
            .drive(outputLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

func bindViewModel() mình khởi tạo một inputs với giá trị như sau:

  • name: inputTextField.rx.text.orEmpty.asObservable() => lắng nghe dữ liệu thay đổi trong text field mà người dùng nhập vào.
  • validate: sayHelloButton.rx.tap.asObservable() => lắng nghe khi nào người dùng sẽ bấm vào nút SayHello để bắt đầu thực hiện hành động tương ứng.

Hi vọng với demo đơn giản này sẽ góp một phần nho nhỏ giúp bạn code trông ngầu hơn 😂.

Cảm ơn bạn đã đọc hết bài viết này ❤️

👇 Resource:

🚀 Pod RxSwift

🚀 Git Final Project