Reusable Views and Models

First, a brief review of what MVC is: Controller is responsible for business processing, network processing, local storage, etc. View is responsible for display, which is invisible and reusable from Model. Model is responsible for data, which is invisible and reusable from View.

How about MVVM: ViewModel can be responsible for business processing, network processing, local storage, packaging Model to View and so on, reuse difficulty; View is responsible for display, which is invisible and reusable from Model. Model is responsible for data, which is invisible and reusable from View. The Controller is treated as a heavy View, which is hard to reuse.

Of the two modes, the only ones that are easy to reuse are View and Model.

Two-way binding

We all know that MVVM requires bidirectional binding, which is the core competency of MVVM.

So what is two-way binding? These are the two directions of the data flow:

View >>> ViewModel >>> Model

View <<< ViewModel <<< Model

Because the ViewModel needs to handle business or data logic, and we don’t need to reuse it, we can hold the Model directly in the ViewModel for binding, so two-way data interaction between them is easy.

Next, how does the View bind to the ViewModel?

Bind View to ViewModel

There is a claim that a View holds a ViewModel for binding. Can such a View be reused? I think it is extremely difficult. In the ViewModel is the logic that handles business, network requests, and so on. If I reuse the View, that means I’ve coupled the ViewModel.

Just think about it,

Scenario 1: Normally, the business logic is in the ViewModel, the ViewModel is bound to the View, and the View holds the ViewModel.

In scenario 2, views need to be reused. The business logic in this scenario is in ViewModel-1. How to set up the binding between View and ViewModel-1? Add another viewModel-1 property to the View.

Scenario 3,…. Add a viewModel-2 property to the View.

Scenario 4,…. Add a viewModel-3 property to the View.

.

Is this View reusable? Of course not. It would be crazy!!

Writing the binding code in the ViewModel would certainly work, but it would coupling the View to the unit test logic, so it would not be a good idea.

So in Controller?

Cool! First, the Controller is hard to unit test, and it needs the ViewModel’s business entry. It needs the View to show the View, which means the Controller holds the ViewModel and the View. Second, controllers themselves are hard to reuse, so code in a Controller is generally not considered to be reusable. So the best place to write the binding code is in the Controller.

The sample

For binding code, you can choose RAC, RxSwift, KVO or notification, block, etc. Here I write a small example using closures (blocks), chosen because there is too much code to do otherwise…

Model

class Model: NSObject {
    var num: Int?
}
Copy the code

View

class View: UIView {
    let numButton = UIButton(a)var numClickBlock: (() -> Void)? // block
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        numButton.backgroundColor = .red
        numButton.addTarget(self, action: #selector(numClick), for: .touchUpInside)
        numButton.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        addSubview(numButton)
        
        // ... other UI coding
    }
    
    @objc func numClick(a) {
        self.numClickBlock?(a)// Events exposed to the outside world
    }
  
   // ...
}

Copy the code

ViewModel

class ViewModel: NSObject {
    private let model = Model(a)var numStr: String = ""
    var updatedBlock: ((String.String) - > ())?
  
    override init(a) {
        super.init()
        model.num = 10; / / initial value
        numStr = "10"
    }
    
    func addAction(a) {
        model.num! + = 1
        numStr = String(model.num!);
        updatedBlock?("add", numStr)  // Notify the outside world of the data update
    }
    
    func subAction(a) {
        model.num! - = 1;
        numStr = String(model.num!);
        updatedBlock?("sub", numStr)
    }
}

Copy the code

Finally, let’s look at the code in the ViewController:

class ViewController: UIViewController {
    var viewModel = ViewModel(a)override func viewDidLoad(a) {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        
        let view = View(frame: CGRect(x: 20, y: 88, width: 100, height: 100))
        self.view.addSubview(view)

        bind(view: view, viewModel: viewModel)
    }

    func bind(view:View.viewModel:ViewModel) {
        
        // View -> bind the ViewModel to the ViewModel
        view.numClickBlock = {
            self.viewModel.addAction()
        }
        
        // ViewModel -> bind the ViewModel to the View
        viewModel.updatedBlock = { evt, numStr in
            view.numButton.setTitle(numStr, for: .normal)
        }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?). {
        viewModel.subAction() // Simulate data updates in business logic}}Copy the code

Looking at the code above, you will see that the reuse of View and Model is very easy, no changes are required, it’s cool.