MVVM with Swift P2

by Hoang Anh Tuan
642 views

Demo MVVM với RxSwift:

Model Layer:

struct Category {
    let id: Int
    var name: String
}

Class Service để thao tác với database:

import Foundation

protocol ICategoryService {
    func getCategoryById(id: Int) -> Category?
}

class CategoryService: ICategoryService {
    func getCategoryById(id: Int) -> Category? {
        let categories = [Category(id: 3, name: "Movies"),
                          Category(id: 4, name: "Books"),
                          Category(id: 5, name: "Computer")]
        
        let category = categories.filter({
            $0.id == id
        }).first
        
        return category
    }
}

ViewModel Layer

import Foundation
import RxSwift

class HomeViewModel {
    var category: Category
    var service: ICategoryService
    
    var displayName: String {
        return transformName(category.name)
    }
    
    // 1
    var valueSubject: PublishSubject<String>
    
    init(category: Category, service: ICategoryService) {
        self.category = category
        self.service = service
        valueSubject = PublishSubject()
    }
    
    func transformName(_ name: String) -> String {
        let newName = name.enumerated().map { (index, character) -> String in
            if index % 2 == 0 {
                return character.uppercased()
            } else {
                return character.lowercased()
            }
        }
        
        return newName.joined()
    }
    
    // 2
    func getCategory(id: Int) {
        if let newCategory = service.getCategoryById(id: id) {
            self.category = newCategory
            valueSubject.onNext(transformName(category.name))
        }
    }
}
  1. Khởi tạo 1 subject kiểu Publish Subject để phát ra các event khi giá trị của category’s name được thay đổi.
  2. ViewModel sẽ thông qua Model Layer để truy xuất đến database. Sau khi lấy được data cần thiết, thì valueSubject sẽ phát ra 1 event có giá trị là name đã được transform của category mới.

View Layer:

import UIKit
import RxSwift

class HomeView: UIViewController {
    @IBOutlet private weak var categoryNameLabel: UILabel!
    
    private var viewModel: HomeViewModel?
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let category = Category(id: 1, name: "Hollywood")
        viewModel = HomeViewModel(category: category, service: CategoryService())
        // 1
        self.categoryNameLabel.text = viewModel?.displayName
        
        // 2
        viewModel?.valueSubject
            .skip(1)
            .subscribeOn(MainScheduler.instance)
            .subscribe(onNext: { (displayName) in
                self.categoryNameLabel.text = displayName
            })
            .disposed(by: disposeBag)
    }
    
    @IBAction func didTapButtonChangeCategory(_ sender: Any) {
        // 3
        viewModel?.getCategory(id: 3)
    }
}
  1. Set text cho label lần đầu khi khởi tạo category.
  2. Lắng nghe khi viewModel phát ra 1 value mới.
  3. Tap button để yêu cầu viewModel lấy ra 1 category có id = 3.

Tác dụng của MVVM:

  • Tương tự như MVP, lợi ích đầu tiên của MVVM là tách biệt phần logic ra khỏi View. Từ đó dẫn đến dễ viết unit test, dễ maintain, …
  • Dễ dàng reuse các ViewModel.
  • Bằng việc tương tác với View thông qua cơ chế data binding, vì vậy không cần tạo thêm nhiều protocol, class…

MVVM vs MVP:

  • Trong MVVM, vì ViewModel tương tác với View bằng data binding, vì vậy ViewModel không có 1 reference nào đến View. Từ đó ViewModel sẽ dễ dàng được reuse, dễ dàng viết test hơn so với MVP.
Presenter và View là liên kết chặt với quan hệ 1:1 trong MVP
Quan hệ giữa ViewModel và View trong MVVM
  • Trong MVVM, 1 View có thể có nhiều ViewModel.
  • MVVM sẽ không cần phải tạo nhiều protocol như MVP.
  • Việc sử dụng cơ chế data binding cũng dẫn đến 1 hệ quả là MVVM sẽ khó để debug hơn nhiều so với việc sử dụng MVP.
  • Nếu sử dụng RxSwift kết hợp với MVVM, thì sẽ phải đòi hỏi team của bạn đều phải biết RxSwift ở mức ổn.

Kết luận:

  • Mục tiêu chính của MVVM là tách biệt logic khỏi View. Tuy nhiên, những phần logic như truy vấn database, networking, … thì chưa được xử lí. Những logic như vậy có thể được đặt ở presenter, hoặc tạo 1 class riêng ở Model tùy theo bản thân bạn.
    Tuy nhiên, nên tách biệt các phần logic Database, networking, … ra các class riêng để tuân thủ nguyên tắc Single Responsibility Principle của SOLID.
  • Theo nguyên tắc MVVM thì ViewModel không import UIKit để tách biệt logic và View.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.