MVVM là một mẫu ngày càng phổ biến hơn, trong khi nhiều ứng dụng hướng sự kiện ngày càng trở nên phổ biến. Có nhiều lợi thế khi sử dụng MVVM so với MVC cổ điển trong phát triển iOS bắt đầu với việc phân chia hoàn toàn logic nghiệp vụ với lớp trình bày. Thay vì có một "ViewController lớn" chịu trách nhiệm thực hiện quá nhiều việc, chúng ta có thể ủy quyền những thứ như yêu cầu dữ liệu từ mạng hoặc cơ sở dữ liệu cục bộ cho một thực thể khác.

https://thepracticaldev.s3.amazonaws.com/i/ibcak32czzov3pwvyskp.png

Tất cả logic ứng dụng này nằm trong ViewModel, không bao giờ biết chế độ xem là gì hoặc chế độ xem làm gì. Điều gì làm cho kiến ​​trúc này trở nên khá dễ kiểm tra và loại bỏ sự phức tạp khỏi chế độ xem khiến nó càng câm càng tốt. 

Chế độ xem sở hữu ViewModel và luôn "lắng nghe" các thay đổi để cập nhật giao diện người dùng. Ở đây có "liên kết dữ liệu hai chiều" nổi tiếng, nếu có sự thay đổi trong chế độ xem, mô hình sẽ được cập nhật và nếu có thay đổi trong mô hình, chế độ xem sẽ được cập nhật, luôn có ViewModel làm trung gian.

Chà, nếu chúng ta thấy điều này trong thực tế thì sao? Hãy tưởng tượng tình huống sau: Tôi muốn phát triển một ứng dụng kết nối với phần còn lại API của Github và cho phép tôi tìm kiếm giữa các kho, chọn một trong số chúng và xem các cam kết gần đây nhất của nó. Bắt đầu nào!

Tôi sẽ bắt đầu với chức năng tìm kiếm, tôi sẽ bỏ qua nhiều thứ cho ngắn gọn, nhưng bạn có thể xem tất cả mã nguồn ở phần cuối. Hãy viết mã giao thức thỏa mãn ViewModel của chúng tôi:

protocol SearchRepositoriesDelegate {
    func searchResultsDidChanged()
}

protocol SearchViewModelType {
    var results: [SearchResult] {get}

    var query: String {get set}

    var delegate: SearchRepositoriesDelegate? {get set }
}

Một thuộc tính cho kết quả, một thuộc tính cho truy vấn truy xuất những kết quả đó và một người được ủy quyền để thông báo cho dạng xem rằng đã có những thay đổi. Đơn giản, phải không?

Xin lưu ý rằng 'SearchRepositoriesDelegate' trong trường hợp khác nên được thay thế bằng một số cơ chế ràng buộc dữ liệu hoặc triển khai các khả năng quan sát, nhưng điều này sẽ dành cho một bài đăng khác.

Khi đó ViewModel của chúng tôi sẽ hoạt động theo cách sau:

1- Chế độ xem sẽ có một tham chiếu đến ViewModel.

2- Trong khi người dùng đang nhập vào thanh tìm kiếm, chế độ xem sẽ cập nhật thuộc tính 'truy vấn' của ViewModel dựa trên truy vấn.

3- Mỗi khi thuộc tính 'query' được cập nhật, ViewModel sẽ yêu cầu phần còn lại của Github API và cập nhật kết quả.

4- Khi có phản hồi của máy chủ, ViewModel thông báo cho chế độ xem (thông qua người được ủy quyền) rằng đã có những thay đổi trong kết quả.

5- Mỗi khi hàm 'searchResultsDidChanged' được gọi, chế độ xem sẽ cập nhật giao diện người dùng.

Hãy xem cách triển khai như thế nào:

ViewModel:

class SearchViewModel : SearchViewModelType {
    var delegate: SearchRepositoriesDelegate?

    var results : [SearchResult] = [] {//0 results by default
        didSet{
            delegate?.searchResultsDidChanged() //notify
        }
    }

    var searchService: SearchService

    var query: String = "" {
        didSet {
            if query == "" {
                results = []
            }else {
                performSearch()
            }
        }
    }

    init(service: SearchService) {
        self.searchService = service
    }

    private func performSearch() {
        searchService.search(query: self.query)
            .onSuccess { results in
                self.results = results
            }.onFailure { error in
                //do nothing
        }
    }
}

View:

class SearchViewController: UIViewController {

    ...

    var searchViewModel: SearchViewModelType!

    override func viewDidLoad() {
        super.viewDidLoad()

        searchViewModel.delegate = self

        ...
    }
}

extension SearchViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        searchViewModel.query = searchText
    }
}

extension SearchViewController: SearchRepositoriesDelegate {
    func searchResultsDidChanged() {
        self.tableView.reloadData()
    }
}

...

Trong khi người dùng đang nhập vào thanh tìm kiếm, thuộc tính 'truy vấn' được cập nhật và ViewModel yêu cầu phía máy chủ. Khi một yêu cầu hoàn tất, nó sẽ thông báo cho chế độ xem và nó gọi hàm 'reloadData ()' của UITableView để phản ánh những thay đổi trong giao diện người dùng. Như bạn có thể đã thấy, lớp SearchViewModel rất dễ kiểm tra vì chúng ta chỉ cần tạo một đối tượng 'SearchService' giả để kiểm tra xem nó có hoạt động chính xác hay không. ViewModel không có tham chiếu đến chế độ xem và chế độ xem được miễn tất cả logic nghiệp vụ.

Bài viết được dịch tại đây