Bài này được mình dịch từ đây

Việc lựa chọn đúng cho mô hình kiến trúc của app là một nhiệm vụ quan trọng.

Kiến trúc tốt sẽ giúp cho việc sửa lỗi được dễ dàng, thêm tính năng mới, và giúp đẩy nhanh tốc độ phát triển phần mềm, mà chi phí phát triển ít hơn, cả trong ngắn hạn và dài hạn.

Với những lý do trên, MVVM (hoặc MVVM-C) có vẻ tốt cho chúng ta, và chúng sẽ giải thích cách sử dụng nó như thế nào. Nhưng trước tiên, hãy đi qua lý do để chúng ta quyết định không dùng kiến trúc MVC của Apple cung cấp.

Những vấn đề với MVC

Từ khi iOS được tạo dựa trên mô hình kiến trúc MVC, nó có vẻ luôn là sự lựa chọn đầu tiên, và ngay cái nhìn đầu tiên nó không đủ sự đơn giản.

Mô hình phân chia phần mềm thành 3 phần khác nhau, trong đó Model đại diện cho data, View đại diện cho giao diện người dùng (user interface) và Controller hoạt động như chất keo kết dính giữa 2 phần còn lại. Điều này có vẻ rõ ràng, nhưng các ứng dụng ngày càng phức tạp, cách tiếp cận này cho thấy những thiếu sót của nó.

Như hướng dẫn của Apple, mọi thứ trong mô hình MVC nên được phân loại là Model, View hoặc Controller, nhưng nó sẽ tạo cho bạn câu hỏi: mọi thứ có thể thực sự được sắp xếp vào 1 trong 3, mà không bị quá tải một phần nào?

Tất cả code và logic thật khó để đặt ở nơi nào khác ngoài ViewController, điều này làm cho chúng ngày càng mở rộng với quá nhiều trách nhiệm. Đổi lại nó trở nên không thể tái sử dụng, khó để bảo trì, dễ bị lỗi và khó khăn hơi để kiểm thử.

Tại sao phải đến với MVVM?

MVVM giải quyết các vấn đề ở trên bằng cách cung cấp một tập hợp các quy tắc rõ ràng hơn và chia nhỏ phần logic của ứng dụng, quản lý các khối nhỏ dễ dàng hơn.

Ở đây, Model chứa tất cả các business logic, giống như lấy data từ database hoặc API, phân tích data và xử lý một số logic tùy chỉnh. Đó là trách nhiệm của business rule, data access, model class

View đại diện cho toàn bộ giao diện người dùng có thể nhìn thấy bởi người dùng và bao gồm cả UIViewControllerUIView.

ViewModel chuẩn bị tất cả thông tin cho giao diện người dùng. Nó giao tiếp với ViewController thông qua một protocol và cung cấp một cách dễ dàng để kiểm thử toàn bộ ViewController.

Ở phương pháp này, ViewController hoạt động như cầu nối giữa ViewModelView và nó không biết gì về business logic.

Kết quả là, ViewController được quy định chặt chẽ, và làm cho quá trình debug và tái sử dụng dễ dàng hơn nhiều.

Nó thực sự làm việc như thế nào?

Cải tiến lớn nhất là kiến trúc MVVM cho phép bạn tổ chức tốt hơn các code thành các khối logic nhỏ hơn.

Tại công ty chúng tôi chia mỗi màn hình thành nhiều ViewController dựa trên mô hình thành phần (Composition Pattern).

Giả sử project của chúng ta có một màn hình hiển thị profile của người dùng, như là Facebook app, và nó có các phần logic sau đây:

  • User profile ở đầu
  • Phần Photo
  • Phần Friend
  • Phần About

Đây là cách chúng ta tổ chức hệ thống phân cấp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
UserProfileContainer (folder)
    UserProfileContainerVC
    UserProfileContainerVM
    UserProfileContainerVMProtocol
    UserProfileHeader (folder)
        UserProfileHeaderVC
        UserProfileHeaderView
        UserProfileHeaderVM
        UserProfileHeaderVMProtocol
    UserProfilePhotos (folder)
        UserProfilePhotosVC
        UserProfilePhotosView
        UserProfilePhotosVM
        UserProfilePhotosVMProtocol
    UserProfileFriends (folder)
        UserProfileFriendsVC
        UserProfileFriendsView
        UserProfileFriendsVM
        UserProfileFriendsVMProtocol
    UserProfileAbout (folder)
        UserProfileAboutVC
        UserProfileAboutView
        UserProfileAboutVM
        UserProfileAboutVMProtocol

Chức năng chính của UserProfileContainerVC là đặt ra cho những ViewController khác, nơi mà mỗi ViewController con chịu trách nhiệm cho một tính năng cụ thể, tuân theo nguyên tắc phân chia mức quan tâm.

Bằng việc áp dụng thành phần ViewController, chúng ta có thể chia một ViewController phức tạp thành nhiều ViewController đơn giản.

Mỗi ViewController đều có các thành phần sau, và đều nằm trong các file riêng biệt:

  • View
  • VM (View Model)
  • VMProtocol (ViewModelProtocol)

Model

Model trong MVVM bao gồm Business LayerAPI layer. Đó là một chủ đề khá quan trọng và phức tạp mà hầu như không thể tóm tắt và giải thích trong một vài ý chính được, vì vậy chúng ta sẽ dành một trong những bài viết tương lai cho nó.

Trong lúc này, chúng ta lướt qua nó một cách nhanh chóng. Business Layer ở đây kiểm soát Data Model và các service làm một vài logic cụ thể mà có thể extractedencapsulated.

API layer thực hiện các protocol được định nghĩa bởi Business Layer, và cũng thực hiện tất cả các yêu cầu HTTP đến máy chủ. Nó cũng có quyền sở hữu các access token. Nó thực hiện logic để nhận được token từ máy chủ, lưu trữ và sử dụng nó với mọi yêu cầu HTTP.

View

Đây là lớp con của UIView và nhiệm vụ của nó là định nghĩa layout cho một ViewController cụ thể.

Khởi tạo của tất cả các button, labelconstraint ở bên trong lớp này và nhiệm vụ của nó là cung cấp các thành phần giao diện người dùng cho ViewController qua các getter.

swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserProfileHeaderView: UIView {

    public let avatarImageView = UIImageView()
    public let backgroundImageView = UIImageView()
    public let fullNameLabel = UILabel()
    public let subtitleLabel = UILabel()
    public let friendRequestButton = UIButton()

    init() {
        super.init(frame:CGRect.zero)
        /*
        configuration of views, constraints, frames etc.
        */
    }
}

Nếu bạn cần thay đổi bất cứ điều gì liên quan đến thiết kế hoặc layout sau này, đây là nơi duy nhất bạn phải tìm.

ViewController tải một view tùy chỉnh được mô tả trong đoạn dưới đây. Ý tưởng cơ bản là để tách việc khởi tạo các yếu tố giao diện người dùng và layout từ việc xử lý các sự kiện khác nhau.

Trong phương pháp này, ViewController chỉ là một giao điểm giữa các yếu tố giao diện người dùng và các data được chuẩn bị bởi ViewModel. Nó không cần phải biết bất cứ điều gì về Business LayerAPI layer.

swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class UserProfileHeaderVC: UIViewController {

    private var userProfileHeaderView: UserProfileHeaderView {
        return self.view as! UserProfileHeaderView 
    }

    private let viewModel: UserProfileHeaderVMProtocol = UserProfileHeaderVM()

    override func loadView() {
        /* Here we connect view controler with our custom view */ 
        view = UserProfileHeaderView()
        /* View controller doesn't need to know anything about layout,
        constraints etc. 
        View controller's task is to handle all events provided
        by user interface */ 
        userProfileHeaderView.friendRequestButton.addTarget(
            self, 
            action: #selector(addFriendRequestAction(_:)),
            forControlEvents: .TouchUpInside )

        /* By calling this method, view controller takes data 
        from its view model  and update its state with the latest data.
        When you need to refresh the data shown by this view controller, 
        all you need is to call this method. Everything else you get for free.
        */
        bindViewModel()
    }
    private func bindViewModel() {
        /* View controller uses its view model to get the correct data */
        userProfileHeaderView.avatarImageView.image = viewModel.avatarImage
        userProfileHeaderView.backgroundImageView.image = viewModel.backgroundImage
        userProfileHeaderView.fullNameLabel.text = viewModel.fullName
        userProfileHeaderView.subtitleLabel.text = viewModel.subtitleText
        userProfileHeaderView.friendRequestButton.enabled = viewModel. friendRequestButtonEnabled
    }
    @objc private func addFriendRequestAction(sender: UIButton) {
        /* we call method on our view model which contain some logic for making this request. */
        viewModel.sendFriendRequest()
    }
}

Để làm cho data binding dễ dàng hơn giữa ViewControllerViewModel, chúng ta thường sử dụng ReactiveCocoa/RxSwift. Chúng ta cũng sẽ đi vào chi tiết hơn về cách sử dụng Xcode InterfaceBuilder (file xib) với kiến trúc này trong những tuần tiếp theo.

ViewModel

Vai trò của ViewModel là để chuẩn bị data cho việc trình diễn, đảm bảo không có sự kết hợp chặt chẽ giữa ModelView.

Khi chúng ta thực hiện kiến trúc này, chúng ta sử dụng VMProtocol, bởi vì nó dễ dàng hơn cho chúng ta để quản lý bất kỳ thay đổi cần thiết khi nhìn vào danh sách các protocol.

Bạn có thể nghĩ VMProtocol như một hợp đồng giữa UIViewControllerBusinessLayer.

Một quy tắc tuyệt vời để kiểm tra là nếu code của bạn thực hiện trong ViewModel đòi hỏi UIKit. Nếu có thì đây là một cảnh báo lớn.

Trường hợp ngoại lệ cho quy tắc này là UIImageCGFloat.

swift
1
2
3
4
5
6
7
8
9
public protocol UserProfileHeaderVMProtocol {

    public var fullName: String { get }
    public var subtitleText: String { get }
    public var backgroundImage: UIImage { get }
    public var avatarImage: UIImage { get }
    public var friendRequestButtonEnabled: Bool { get }
    public func sendFriendRequest()
}

ViewModel thực thi thường gọi các class từ Business Layer, nó giao tiếp với Model của ứng dụng…

swift
1
2
3
4
5
6
7
public class UserProfileHeaderVM: NSObject, UserProfileHeaderVMProtocol {
    /*
        Here the class implements UserProfileHeaderVMProtocol protocol. 
        Implementation is application (project) depended and 
        it doesn't influence this architecture. 
    */
}

MVVM là một mô hình tuyệt vời, và làm cho cuộc sống của bạn dễ dàng hơn khi bạn hiểu về nó. Tuy nhiên, nó không phải là một câu trả lời tổng quát cho tất cả các vấn đề. Hãy theo dõi cho bài sau, khi chúng ta nói nhiều hơn về MVVM-C, C là Coordinator.