12/08/2018, 13:29

MVVM Application

Tiếp theo bài viết trước về MVP, tôi sẽ trình bày về mô hình MVVM và áp dụng của nó trong ứng dụng iOS. MVVM Tôi biết về mô hình này khi lập trình WPF. MVVM được sáng tạo bởi hai kỹ sư của Microsoft là Ken Cooper và Ted Peters với mục đích làm đơn giản việc lập trình sự kiện của giao diện người ...

Tiếp theo bài viết trước về MVP, tôi sẽ trình bày về mô hình MVVM và áp dụng của nó trong ứng dụng iOS.

MVVM

Tôi biết về mô hình này khi lập trình WPF. MVVM được sáng tạo bởi hai kỹ sư của Microsoft là Ken Cooper và Ted Peters với mục đích làm đơn giản việc lập trình sự kiện của giao diện người dùng dựa trên các tính năng đặc biệt của WPF và Silverlight.

View: Tương tự như trong mô hình MVC, View là phần giao diện của ứng dụng để hiển thị dữ liệu và nhận tương tác của người dùng. Một điểm khác biệt so với các ứng dụng truyền thống là View trong mô hình này tích cực hơn. Nó có khả năng thực hiện các hành vi và phản hồi lại người dùng thông qua tính năng binding, command.

Model: Cũng tương tự như trong mô hình MVC. Model là các đối tượng giúp truy xuất và thao tác trên dữ liệu thực sự.

ViewModel: Lớp trung gian giữa View và Model. ViewModel có thể được xem là thành phần thay thế cho Controller trong mô hình MVC. Nó chứa các mã lệnh cần thiết để thực hiện data binding, command.

0F8CE9002FE88CE9B6D6C2FDAB5F7B65.png

MVVM khá tương đồng với MVP:

  • MVVM coi view controller như là View
  • View và Model không ràng buộc chặt với nhau

View và Model được "bind" với nhau, nghĩa là thay đổi ở Model sẽ được cập nhật ở View và ngược lại.

Binding

Do iOS không hỗ trợ binding nên chúng ta phải chọn một trong các phương án sau:

  • Dùng một trong các thư viện binding dựa trên KVO như RZDataBinding hay SwiftBond
  • Sử dụng các FRF (functional reactive programming) framework như ReactiveCocoa, RxSwift hay PromiseKit

Trong đó phương án dùng ReactiveCocoa là phương án được nhiều người lựa chọn nhất. Tuy nhiên việc sử dụng FRF framework cho 1 chương trình demo đơn giản thì hơi quá sức. Trong khuôn khổ bài viết này tôi sẽ viết một ứng dụng đơn giản sử dụng mô hình MVVM.

Tạo mới dự án

Chúng ta sẽ tạo một project Single View Application mới với các thông số như sau:

F82C6E3EAB18F3CCF49D0B74C8401460.png

Storyboard

Để đơn giản chúng ta chỉ có một màn hình hiển thị danh sách sản phẩm, sử dụng UITableViewController

CF6C92F08B1CEADF373D2D15381BA646.png

Model

Model của chúng ta là sản phẩm:

import UIKit

struct Product {
    var name: String
    var price: Double
}

ViewModel

ViewModel chứa data source cho table view của View và hàm hiển thị danh sách product. Trong ViewModel, hàm event productsDidChange để thông báo cho View mỗi khi data source thay đổi dữ liệu.

protocol ProductListViewModelProtocol: class {
    var products : [Product] { get }
    var productListDataSource: ProductListDataSource! { get set }
    var productsDidChange: ((ProductListViewModelProtocol) -> ())? { get set }
    init(products: [Product])
    func showProductList()
}
class ProductListViewModel: ProductListViewModelProtocol {
    private(set) var products: [Product]

    var productListDataSource: ProductListDataSource! {
        didSet {
            self.productsDidChange?(self)
        }
    }

    var productsDidChange: ((ProductListViewModelProtocol) -> ())?

    required init(products: [Product]) {
        self.products = products
    }

    func showProductList() {
        productListDataSource = ProductListDataSource(products: products)
    }
}

Chúng ta sử dụng data source để đưa dữ liệu vào data table:

class ProductListDataSource: NSObject, UITableViewDataSource {
    var products: [Product]

    init(products: [Product]) {
        self.products = products
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return products.count
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("ProductCell", forIndexPath: indexPath)
        let product = products[indexPath.row]
        cell.textLabel?.text = product.name
        cell.detailTextLabel?.text = String(product.price)
        return cell
    }
}

View

Trong ví dụ này, ProductListViewController đóng vai trò là view với các đoạn code đơn giản như sau:

class ProductListViewController: UITableViewController {

    var viewModel: ProductListViewModelProtocol! {
        didSet {
            self.viewModel.productsDidChange = { [unowned self] viewModel in
                self.tableView.dataSource = self.viewModel.productListDataSource
                self.tableView.reloadData()
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.showProductList()
    }
}

Mix

Công đoạn kết nối các thành phần với nhau ta sẽ thực hiện ở AppDelegate:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    let nc = self.window?.rootViewController as! UINavigationController
    let productListViewController = nc.topViewController as! ProductListViewController // view

    let products = [ // model
        Product(name: "Keyboard", price: 6),
        Product(name: "Mouse", price: 5)
    ]

    let viewModel = ProductListViewModel(products: products)
    productListViewController.viewModel = viewModel

    return true
}

Mối quan hệ giữa các thành phần lúc này như sau:

E0E94605CB0CD69E7612189EFD8EBCFE.png

Chạy demo:

90D478903A0AD9D3D350164B8C9DE30C.png

Như vậy là chúng ta đã hoàn thành việc tìm hiểu mô hình MVVM, các bạn có thể hoàn thiện chương trình bằng cách thêm các tính năng thêm, sửa, xóa sản phẩm.

Source code

0