preface

Allow enough time, you’ll see plenty of text. But trust me, it’s well worth your time. I’ve done my best to talk about why MVC and MVVM are designed the way they are and how should we think about some of these questions

Let’s start with MVC

I have to take this picture from the Stanford open Class to illustrate MVC, which is probably the most classic and canonical MVC standard

So if you understand this diagram, you should get a sense of how MVC is implemented in iOS.

You’ve been using the idea of MVC all along but you probably didn’t realize it

Before we dive into MVC, there are a few things you should know, and you probably already know them, but I’m going to mention them here, because they’ll help you understand how MVC works. Almost all apps do one thing: show the user the data and handle what the user does with the interface. Those of you who have written iOS App development know that a lot of what we do is we write layout code in the Controller, display data to the view, and then implement some logic that deals specifically with user actions (such as the implementation of button click events). Here you’re actually using the MVC idea invisibly: The Controller is responsible for displaying the Model data in the View. In other words, the Controller assigns the Model data to the View. For example, write self.label.text = self.data[@” title “] in controller, but you haven’t created a Model class yet.

How does MVC work

M: Model, data Model, for example, we humans have a pair of hands, a pair of eyes, a head, no tail, this is the Model, the Model defines the data Model of this module. In the code, the Model is the data manager, and the Model is responsible for obtaining and storing the data. Data cannot be generated out of thin air, either the data obtained from the server, or the data in the local database, or the user may fill in the FORM on the UI will be uploaded to the server for storage, so the data source is needed. Since the Model is the data manager, it is naturally responsible for obtaining the data. The Controller doesn’t care how the Model gets the data, it just calls it. The data is stored in the Model, and the data is used in the Controller, so the Model should provide an interface for the Controller to access the data (usually through the read-only property in.h).

V: View, basically, everything we see on the interface. Most of the time it’s an object that inherits from a UIView class, and sometimes it’s not directly inheriting from UIView, and sometimes it’s directly drawing with CoreAnimation or even CoreGraphics, but it all boils down to views in MVC. Some of them are UI fixed, which means they don’t update based on the data, such as some Logo images, there’s a button, there’s an input field, some labels that show certain content, etc. Some of them are going to display things based on data, like a tableView showing a list of friends, so this tableView is going to display things based on data. When we use MVC to solve a problem, we usually solve these views that display content based on data. A word of warning: MVC though seemingly the module is divided into three parts, but is not to say that a module to a certain building three classes, such as the contact list (ContactsList) module, will definitely ContactsListViewController, then we use the MVC as a framework for the internal, Then you need to create the ContactsListModel class, and then some kid will go ahead and create the ContactsListView, and he thinks this is MVC, and there are three classes, XXXModel, XXXController, XXXView, and that’s a big mistake. The UILabel, UITableView, and so on that you use in your Controller are MVC views. Don’t create an XXXXView, it’s just a chore.

C: Controller, the most familiar and abstract thing. And the reason that you’re familiar with that is that when we’re developing with UIKit, I’ll say the class that you deal with the most: UIViewController. The UIKit framework is inseparable from UIViewController, and of course you can use UIView to do all the things that should be done by the Controller, which is the wrong way to do it, because doing so would mess up your entire project code and perfectly violate the idea of object orientation: each person should do his or her job. The important thing to know here is what the UIViewController is supposed to do, which is actually the interface provided by the API: self.view is used as a container for all the views; Manage your own lifecycle; Transitions between controllers; The controller container. This is the Controller’s job, but it often also acts as the data and view coordinator in MVC, In other words, the Controller assigns data from the Model to the View to display (or the View receives input from the user and the Controller passes the data to the Model to save locally or upload to the server).

summary

In fact, you should be able to deduce the reason for controller from the basic idea of object orientation: All of our apps are interfaces with data, so we need classes to draw interfaces, so we have views, and we need classes to manage data, so we have Models. We should design the View to be able to display any content for example the UILabel text should be arbitrary and not just the content of a particular Model, so we shouldn’t write any Model related code in the implementation of the View, because if we do that, then the View will have very low scalability. And the Model is responsible for processing data only, it does not know the data in time will take to do, as scratching may take go to, may take to display to the user, since it can’t receive the user’s interaction, it should not be any information related to pipe and views, so the Model should not write any View in the relevant code. However, we need to synchronize our data with the interface, which means there must be a place to assign the Model data to the View, and it is impossible to write such code inside the Model or inside the View, so we have to create a new class called Controller. It was gradually perfected by UIKit into the UIViewController that we use today.

Look at the picture speak

You’re asking me how I know all this stuff? I’ve only done one look and talk, and we’re finally going to go through the picture, and I’m going to show you the picture again for a better viewing experience, so you don’t have to scroll through it for a long time.

Without further further, this graph divides MVC into three separate areas, separated by some lines. Interesting design, because these lines seem to appear in driving school subject one, and you see the white lines between C and V and C and M, are partly dotted and partly solid, right? And that shows the referential relationship: C can refer directly to V and M, and V and M can’t refer directly to C, at least you can’t explicitly write any code related to C in the code of V and M, and there’s a double yellow line between V and M, yeah, they can’t refer to each other, you can’t write V in M, you can’t write M in V. Oh, the above description is a little small problem, you are not “can’t”, but “shouldn’t” like this, no one can stop you when you write the code to write in a M V, but once you do, you will violate the specification of MVC, you are not in the use of MVC, so it is a necessary condition of MVC: Use MVC — > M with no V code. So the fact that there’s no V code in M is a requirement to use MVC, so if P->Q, then ~Q->~P, you know, we did it in middle school or high school, if you write V code in M, then you’re not using MVC. I’ve seen before where a dictionary turns a model and then assigns a model to a Cell that parsed the model inside the Cell to display it, so I can only say that you’re not using MVC.

I made it clear at the beginning what they were and what they did. So now let’s look at how they interact, which you can think of as how to pass values.

View and Controller interaction

In iOS, passing includes passing events, like a button click event, which is received by the View, but it’s supposed to be handled by the Controller, so the View passes this event to the Controller, how do you pass it, see the figure, you don’t see the action on the View, this is the event, You see the target on the Controller, that’s the target, who exactly does the View pass the event to, it’s defined to pass to the target, the Controller is actually the target, what do you write when you add click events to buttons? Button to add target: I event: XXX… ; It’s just that the View is only responsible for passing the event, and it doesn’t care who the target is. It’s like if you’re a teenager in charge of a shipment, the only thing you know is that you’re delivering the action to target, and you don’t care whether that action is a cop or a monster. This is one way that V and C interact, and it’s called target-action. So you can see, this is a masterstroke, and it also shows another kind of pass between V and C: protocol-delegate. There are two kinds of delegates: agents and data sources. What is a proxy, which is a delegate that handles should, will, and DID events, and what is a datasource, which is a delegate that handles data, count, and so on. The one you use the most, you use tableView, you don’t, you don’t dare say you do iOS development? Have you ever wondered why tableViews need data sources to implement protocol methods instead of just passing data directly to the tableView through properties? If you come here for an interview, congratulations, you will be lucky enough to be asked this question.

TableView is not as simple view that displays the contents of a simple, it will show the rich content, its own all don’t know, it is designed to display as many groups, each group any number of cells, each cell can show any content, even above the height of each cell is different, and so on, such harsh conditions, It’s definitely not a simple attribute assignment. However, something similar has been around for a long time in the C library: our awesome sorting function. This sort function is designed to sort any type of array, whether it’s an integer array, a string array, or any of your fancy structures. Yes, if you’ve learned callbacks, then you’ve seen this awesome sorting example. The only problem with sorting arrays of any type is the way the elements in the array are compared. In fact, this problem can be described in code: the sorting function S needs to continuously compare two elements in the array, and S will sort by the result of the comparison. It means that S only needs to compare the results, and how to compare is determined by the callback function provided by the caller. You ask the employee to sort the array for you, and you don’t know how to compare the contents of the array, so what am I doing? So problem solved: S functions need to be provided by the caller a function pointer as the argument, the function pointer to the function receives two parameters and returns the results of the two parameters comparison, S just in need of more calls the function pointer to the function, the incoming S want to compare the two elements, to get the return of the comparison results.

So, see how tableViews can be so demanding: I need the methods from the caller to draw, I want the results, not the process! I’m going to tell you by argument, I’m drawing section 1, how many cells are there under section? Which is called [self. The dataSource tableView: self numberOfRowsInSection: section] the return value is how many cell under section, tableView only need to call a method to obtain the result, The logic for drawing and processing the data is in the dataSource. Just like a kid, the tableView keeps asking questions, the dataSource keeps answering the tableView’s questions, and when the questions are done, the tableView is drawn. Once the tableView is designed this way, any class can call it, but there’s a condition for calling a tableView, and that is you have to be able to solve my problem, and that’s where the protocol was born: you have to be able to answer my question. By answering the question on the tableView, you’re implementing the protocol that the tableView declares.

The dataSource makes the drawing logic controlled by the dataSource (the implementation of the dataSource protocol methods) and the drawing process done by V (the invocation of the dataSource protocol methods) in the form of callbacks. The caller V passes the value to the dataSource through parameters, and the implementer passes the value back to the caller through the return value. In this way, V gets the drawing information it needs by constantly calling the dataSource method, and finally draws the interface.

Often, V’s dataSource is a C, and C implements the dataSource protocol by using the data in M, which is equivalent to C indirectly assigning M to V.

Similarly, the delegate protocol is a callback that handles more events. The words “should”, “will”, “did” are all forms of asking, “should”, “am”, “have”, “have”… When such a request has occurred (i.e., the moment the scrollView is about to be dragged) When scrollView stops decelerating (didEndDecelarating…) Etc.), V calls the corresponding delegate method. For example, the click event on a tableView Cell is received directly by V (because the user is directly working on the object V), and the place to process the click event is in C, so V should somehow tell C that a Cell was clicked (didSelectRowAtIndexPath…). And you have to tell C which Cell (indexPath) is being clicked, so when the Cell is clicked, V calls the delegate’s protocol method, so that the delegate handles the click event. It tells the delegate which cell was clicked this time. In short, a protocol has been agreed between V and its delegate, and once V is and has been, the protocol implementation will be implemented. By following the protocol implementation, we mean having the delegate call the protocol methods. This means that the corresponding implementation of the protocol method in the delegate will be called as soon as V will or has been.

This is how V passes values to C. To sum up, there are three main ways to do this: action-target is used to pass specific events; Datasource-protocol is used to dynamically draw data through the interface in the form of callbacks. Delegate-protocol specifies the rules for handling events in advance. When a specified event occurs, it is handled according to the protocol. A protocol delegate can pass values from V to C through the parameters of the protocol method. For example, the protocol method for the cell click event, the tableView tells C which cell was clicked with the indexPath parameter.

Model and Controller interaction

Let’s take a look at the most controversial interaction between M and C since the birth of MVC.

Let’s start with what M does.

What does M do? As mentioned above, M stands for data manager, which you can understand as it deals directly with the database. It could be a local database, it could be a server database, and M is going to get the data from the database, or it’s going to upload the data to the database. M will also provide properties or interfaces for C to access the data it holds. Let’s take a simple requirement for example. Let’s say I want to display a piece of text in a module that is taken from the Internet.

So if you’re using MVC, you’re going to need a UILabel (V) property in C to display this text, and who’s going to get this text? M is going to get this text. And where do you get it? It’s usually in the lifecycle of C, so it’s usually going to be in one of the lifecycle methods of C like viewDidLoad to call M to get the data. Now the question is, M gets the data by making an asynchronous network request. After the network request ends, C should reassign the data to V using the request. Now the question is, how does C know when the network request is over?

Here we must think differently. Let’s consider the relationship between M and V: they should be a synchronous relationship, that is, whenever the value of M changes, the display of V should change (display the latest contents of M). So we can pay attention to the value of M changing, and we don’t care if M’s network request is over. In fact, C has no idea where M got the data from, and C is responsible for assigning M’s latest data to V. So the event C should be concerned with is whether the value of M has changed.

So we just need to figure out how C knows that the value of M has changed.

Fortunately, there is a mechanism in OC that happens to solve the problem of one object wanting to care about whether the properties of another object have changed. It is called KVO. (see photo)

KVO called key observation, it makes an object as an observer to observe another object by a key value path is represented by the attribute, once this attribute has changed, the system is called the observer a method called observingValueForKeyPath:… . For example, if C wants to refresh the interface after the data property of M has changed, then it only needs to add observer C to M, and the observation path is @ “data”. In this way, C’s observingValueForKeyPath method will be called once m.ata has changed. I can say in the implementation of this method self.label.text = self.model.data; So we’re going to synchronize M and V.

It also shows something called Notification which means Notification, for example, if you want your network request to fail you should have a prompt box, or if you want to automatically log in and you want to open your App and request the home page data to fail and you want to go back to the login page to log in again, you should definitely do that in C, so if M’s network request fails, You can send a notification to C telling C that the network request failed, and you can do it yourself.

And the reason I say this is controversial is because of the block. Block is the perfect solution to some of the problems with callback implementation, Block callback is quite convenient and simple. It’s perfectly possible for C to pass a block to M, and M to call the block after the network request ends. However, to do so, I personally think it goes against M’s design philosophy and object oriented thinking. M should only update its own data after the network requests for data, it should not call a block, it does not know that it is owned by A C, so for it, it cannot actively call anything, it can only passively tell C without knowing that my data has changed. KVO, on the other hand, happens to be passive with respect to the object being observed: M doesn’t even know it’s being observed, and M routinely assigns new data to its properties without doing anything else it shouldn’t.

This is my personal understanding. Of course, using blocks is a lot easier to implement in code and a little more efficient at run time than KVO, which creates classes dynamically at run time. But I would personally prefer to use KVO in MVC rather than block. Different people have different opinions, and a good way to solve a problem is a good way, so I won’t discuss it here.

A word is not in the code

So with all of this, let’s do a demo. I’m going to simulate a network request to get the data using GCD’s delayed approach, using MVC to implement an asynchronous network request and refresh a table view after getting the data.

Preparation: Since you are using MVC, it is essential that you first need a Model class. The name of the class can be determined by the current module. For example, if the module is a news list, then it can simply be called NewsModel. Of course, the class name should be prefixed with its own name to make it more standard.

Once the class is built, we should start implementing it.

How to implement MVC, you can do exactly the same thing as the picture above. First of all, the Model is responsible for dealing with data, and more specifically, storing and fetching data. Fetching is to use methods to make network requests or to deal with local databases, and storing is to put fetched data into a property of its own for external access. As a matter of course, the Model header file should have two methods: a read-only property for storing data and an instance method for fetching data:

@interface DHNewsModel : NSObject

@property (nonatomic, strong, readonly) id dataList;

- (void)getData;

@end
Copy the code

Since we usually don’t know what the data is, the attribute is declared with an ID.

And in dot m, it’s a simple implementation. The getData method just calls the network request, you can use the URL Session of the system, you can use AFNetworking or you can encapsulate CFNetwork. I’ll simply simulate a network request. The effect of a network request is to delay the acquisition of a piece of data, so my getData method is implemented this way.

@interface DHNewsModel () @property (nonatomic, strong) id dataList; @end @implementation DHNewsModel - (void)getData { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (3 * NSEC_PER_SEC)), dispatch_get_main_queue (), ^ {self. The dataList = @ [@ {@ "title" : @ "news," @ "date:" @ "2016-01-25", @"image":@"http://g.hiphotos.baidu.com/image/h%3D300/sign=bd5cccb88b5494ee982209191df4e0e1/c2cec3fdfc039245980aac088094a 4 c27d1e257d. JPG "@" content ": @" blablabla "}, @ {@ "title" : @ "news," @ "date:" @ "2016-01-27", @"image":@"http://a.hiphotos.baidu.com/image/h%3D300/sign=8d9d3903900a304e4d22a6fae1c9a7c3/ac4bd11373f082022a2ddc384cfbf bedab641b7d.jpg", @"content":@"ahahaha"}]; }); } @endCopy the code

So that’s our Model. Again, the Model is only responsible for storing and fetching data. We have implemented getData, and we have implemented storing data (assigning values to the dataList after obtaining data).

The next step is the implementation of Controller.

Our C should hold M and V, so it should have a Model property. The interaction between C and M should look like this: C observes M’s properties and refreshes the interface in the KVO callback. And the interaction between V and M looks like this: in C, C uses the properties of M to assign values to the properties of V.

So our implementation of C looks like this:

#import "ViewController.h" #import "DHNewsModel.h" @interface ViewController () <UITableViewDataSource, UITableViewDelegate> @property (nonatomic, strong) UITableView * tableView; @property (nonatomic, strong) DHNewsModel * model; - (void)_registerObeserver; - (void)_unregisterObserver; @end @implementation ViewController - (void)dealloc { [self _unregisterObserver]; } - (instancetype)init { self = [super init]; if (self) { } return self; } - (void)viewDidLoad { [super viewDidLoad]; [self.view addSubview:self.tableView]; // 1, I want to request data [self.model getData]; // KVO [self _registerObeserver]; // I should receive a callback after the data request was successful (after the model data was updated) and refresh the interface with the model's latest data // Model and view should be synchronized at any time // KVO [self _registerObeserver]; } #pragma mark - private methods - (void)_registerObeserver { [self.model addObserver:self forKeyPath:@"dataList" options:NSKeyValueObservingOptionNew context:nil]; } - (void)_unregisterObserver { [self.model removeObserver:self forKeyPath:@"dataList"]; } #pragma mark - callback - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self.tableView reloadData]; } - (UITableView *)tableView { if (! _tableView) { _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; _tableView.dataSource = self; _tableView.delegate = self; _tableView.tableFooterView = [[UIView alloc] init]; } return _tableView; } #pragma mark - getter - (DHNewsModel *)model { if (! _model) { _model = [[DHNewsModel alloc] init]; } return _model; } #pragma mark - UITableViewProtocol - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.model.dataList count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellIdf"]; if (! cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellIdf"]; } NSDictionary * infoDic = self.model.dataList[indexPath.row]; cell.textLabel.text = infoDic[@"title"]; cell.detailTextLabel.text = infoDic[@"date"]; NSURL * imageUrl = [NSURL URLWithString:infoDic[@"image"]]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSData * imageData = [NSData dataWithContentsOfURL:imageUrl]; UIImage * image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ cell.imageView.image = image; }); }); return cell; } @endCopy the code

We’re using KVO to synchronize V and M. Again, you can use blocks for callbacks, but I’m just saying that KVO is more in line with M’s design.

MVVM

What is MVVM (what)

MVVM: Model, View, and ViewModel.

You’ll subconsciously compare it to MVC, and you’ll see that MVVM has one more ViewModel and one less Controller.

Let’s start with the extra ViewModel (VM, not video memory). VM, like Model, is about data. The Model is responsible for fetching and storing data. However, in addition to fetching and storing data, we have one very important operation: parsing.

Why MVVM exists and why MVVM is used to solve some problems

Imagine that the data that our Model is fetching is the system time, which is an object of type NSDate, and the Controller needs a UILabel to display that time, and in this case, since the Model is storing the NSDate, So when the Controller assigns a value to the Label it needs to convert an NSDate to an NSString, and that conversion is called data parsing. A more specific interpretation of data parsing: converting raw data into data that the View can use directly. For example, a UILabel directly uses an NSString rather than the original NSDate stored in the Model.

A more common example would be a network request to retrieve a dictionary (which is usually what JSON encapsulated data is), which is stored in the Model as raw data. And our Controller actually needs an array of keys in the dictionary, and then uses that array to control the display of a UITableView.

The above example shows that we often put data parsing operations into the Controller without realizing it. Just as we analyzed how MVC allocates work properly, we need data so we have M, we need an interface so we have V, and we need to find a place to assign M to V to display, so we have C, but we missed one very important operation: data parsing. In the era when MVC was born, the data of mobile apps was often relatively simple and not as complex as it is now, so the data parsing at that time was likely to be solved in one step, so since there was such a problem to deal with, and the object-oriented idea was to solve the problem with classes and objects, obviously V and M had been defined long ago, Neither should deal with the problem of “parsing data,” which is, of course, left to C. Nowadays, mobile apps have more and more complex functions and data structures, so data parsing is not so simple. If we continue with the MVC design and put the data parsing part in the Controller, the Controller will become rather bloated. It is also important to note that the Controller is not designed to handle data parsing. The Controller can do some of the things that have been said before, and again, according to the UIKit framework API definition of the UIViewController header: 1. Self. view is used as a container for all views; 2. 2. Manage your life cycle; 3. Handle jumps between controllers 4. Implement the Controller container. There is no “data parsing” at all, so obviously data parsing should not be done by the Controller. So in our MVC, M, V, C should not handle data parsing, so who will? This question is actually pretty easy to answer when it comes to object orientation: since there is no class that can handle the problem, why not create a new class to solve it? So our clever developers created a new class specifically for data parsing: ViewModel. This was the birth of MVVM.

How to implement MVVM

Understanding why MVVM exists will help you tremendously in understanding how to implement MVVM. Before we start implementing MVVM, LET me briefly mention one of the remaining questions: why is there no Controller in the name MVVM (why not MVCVM, where did C go?). Originally, this problem should be explained after the implementation, but we are teaching here, so in order to better understand what we are going to think, so I want to explain the conclusion in advance: the Controller’s presence has been completely reduced. You’ll see this later when we implement MVVM, but keep this in mind: The Controller’s presence is completely reduced, the Controller’s presence is completely reduced, the Controller’s presence is completely reduced.

Okay, we’re finally getting started on implementing MVVM. If you already understand MVC, implementing the same functionality with MVVM will be very simple. The only two things you need to keep in mind are: 1) the Controller’s presence is completely reduced; 2. The appearance of VM is the reason why the Controller’s presence is reduced.

Let’s start with some theoretical preparation

In MVVM, the Controller no longer directly holds the Model as MVC does. Imagine that the Controller is a Boss, and the data is a bunch of files (Model). If it’s AN MVC, then the data parsing (such as file sorting) needs to be done by the Boss himself. In fact, the Boss only needs the files that have been sorted, not the piles of files that have been sorted before. So the Boss hires a secretary, and now the Boss doesn’t need to manage raw data anymore, he just needs to go to the secretary: You help me organize the files and bring them back to me. Then the secretary first gets the file (raw data), then organizes it (data parsing), and then sends the result to the Boss. So the secretary is the VM, and Controller (Boss) now only needs to directly hold the VM instead of M. If you further understand the relationship between C, VM, and M: Since the Controller only needs the result of data parsing and does not care about the process, it is equivalent to the VM encapsulating “how to parse the Model”. C does not even need to know that M exists in order to get the job done, provided that it needs to hold a VM. So our holding relationship in MVVM is: C holds VM, VM holds M. There is a controversy over whether C should hold M. My answer is no. Why? Because it doesn’t make any sense for C to hold M. Even if C gets M’s data directly, it still has to ask the VM to parse the data, and data parsing requires M, so it’s enough for THE VM to hold M and C to hold the VM directly. Finally, one of my tips for implementing MVVM, which is not a trick, but a necessary idea: Whenever you have any questions related to the Model (or data) while implementing Controller, look to the VM for answers. This is an idea that we’re going to use in our implementation code.

It’s not code 2.0

Once you’ve figured out the relationship, you can finally start working on it. Obviously, MVVM also needs a Model, and the Model in MVVM is exactly the same as the Model in MVC, so we can leave the Model in our last MVC demo unchanged by one line of code, and then we need to create a new class, which is called DHNewsViewModel. Keep in mind that THE VM is like C’s secretary, and what it does depends entirely on C’s commands. So if you don’t know what to write in the VM at first, just leave it there, and when C runs into any problems with data, you’ll know what the VM should provide.

The models from our previous MVC demo can be used directly, so when writing the MVVM demo here you can either create a new project and copy the MVC Model into the new project or directly modify the original MVC project to implement MVVM.

Either way you implement MVVM, you will need to implement the Controller code again, so if you make changes to the original project, you will need to delete the Controller code, as if you had just created a Controller class file. Keep only the extended declaration and an empty implementation of the ViewDidLoad method (not required if based on a new project).

Now that we have the complete Model in our project, we know that the VM should hold an M, so we can write an extension in.m to declare a private M property.

#import "DHNewsViewModel.h"
#import "DHNewsModel.h"
#import <UIKit/UIKit.h>

@interface DHNewsViewModel ()

@property (nonatomic, strong) DHNewsModel * model;

@end
Copy the code

In our previous design, the Controller should have a ViewModel that is responsible for data parsing in addition to its routine (initializing various views and assigning values to them). So you can build the ViewModel class, and then you can write a private property in the Controller. I’m going to call the ViewModel class DHNewsViewModel. Then in the Controller implementation file, the first is extension:

#import "ViewController.h"
#import "DHNewsViewModel.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView * tableView;
@property (nonatomic, strong) DHNewsViewModel * viewModel;

- (void)_registerObeserver;
- (void)_unregisterObserver;

@end
Copy the code

You can compare MVC to just having a ViewModel instead of having a Model. Then we implement private methods to register and remove KVO.

Before implementation, we need to know what the observer’s keyPath is.

To solve this problem, we should first think about what effect we want. In this case, we want the Controller to be able to observe changes to a property called dataList in the Model. So you should set up a Controller keyPath path to this property. Since our holding relationship is that the Controller holds the ViewModel and the ViewModel holds the Model, we can only add observers to the ViewModel by registering observers in the Controller. We’re looking at the properties of the Model, and the place to add the observer is in the Controller, and the Controller doesn’t know how the ViewModel holds the Model (the ViewModel holds the name of the Model property, or the variable name, Because the keyPath observation path is related to the property name), and the Controller doesn’t know the name of the property in the Model that it wants to observe (I’m anthropomorphizing the Controller, although we as developers do know the names of all the properties that we can write in the Controller, But from an object-oriented point of view, this information should be encapsulated, so the Controller as a class cannot know this information, and information it should not know should not be written in the Controller.) In this case, the value of the keyPath should be roughly @ “the name of the model variable held in vieModel and the name of the data variable in the model”, both of which are unknown to the Controller. So how do we add observers? Description: I spent a lot of seemingly unnecessary described above, just to tell you that must be the formation of such thoughts, for a class, it should not be know, shouldn’t do, we don’t get it, don’t let it go to do, the reason for this design, mainly because of the coupling problem, somewhere after modification will not affect other places; And when there is a problem, it can quickly identify where the problem is, because all the classes have their own duties, and the division of labor is clear. If all classes are involved in the problem, it’s hard to figure out which class is the problem. The more information a class exposes, the more places it can be used, which obviously leads to higher coupling, and the more “confusing” it is to use. This class has so many properties in its.h, what’s the meaning of these properties? What’s going to happen to this property if I don’t assign it? This creates a lot of confusion for the caller or the implemancer of the business logic, which is obviously not good for development. So some of the properties, the readONLY is readONLY, and the properties that are private have to be private.

Okay, so since the Controller doesn’t know the path of the observer, how do you register the observer? Here we go a little further, the Controller doesn’t know the path because it’s looking at the Model stuff, remember that idea I mentioned above? The Controller can ask the ViewModel for any problems related to the Model. So, our keyPath should be provided by the ViewModel with a method that the Controller calls.

Here I recommend you to first clarify the above relationship, from the extended @end to the last paragraph, the text in the middle I hope you can think about the summary. Finally, if you understand why the ViewModel should provide a method to the Controller to get the path of the observer, then the next steps are pretty clear.

So we can add a method inside the.h of the ViewModel

- (NSString *)observingKeyPath;

Copy the code

This method returns the observer path, which can be implemented directly in.m:

- (NSString *)observingKeyPath
{
    return @"model.dataList";
}
Copy the code

And then we call it back in Controller.

#pragma mark - private methods
- (void)_registerObeserver
{
    [self.viewModel addObserver:self forKeyPath:[self.viewModel observingKeyPath] options:NSKeyValueObservingOptionNew context:nil];
}

- (void)_unregisterObserver
{
    [self.viewModel removeObserver:self forKeyPath:[self.viewModel observingKeyPath]];
}   

Copy the code

Remember to call the unregister method in dealloc.

So that’s a direct call

[the self. The viewModel addObserver: self forKeyPath: @ "model. The dataList" options: NSKeyValueObservingOptionNew context: nil];Copy the code

Now that we know that, the rest of the action is, in effect, “Whatever the Controller wants, the ViewModel will give.” If the boss wants it, the secretary gives it.

Let’s go ahead and implement Controller. So once you’ve written the private method, you call the registered observer inside ViewDidLoad. A network request is also made to retrieve the data. Yeah, here we go again. The network request method is in the Model, and the Controller can’t get the Model, so how do you make the network request in the Controller’s ViewDidLoad method? Same answer: The ViewModel provides methods for the Controller to call. “Don’t you want a web request? I can call Model to make a network request, so I’ll give you a method that you can call when you want a network request, and I’m essentially asking Model to make a network request.” The ViewModel says to the Controller.

So we declare a new method in the.h of the ViewModel:

- (void)getData;

Copy the code

The implementation is simple:

- (void)getData
{
    [self.model getData];
}
Copy the code

So in the Controller’s ViewDidLoad we just call the ViewModel getData method, which is equivalent to calling the Model getData method, and we start the network request:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.tableView];
    [self.viewModel getData];
    [self _registerObeserver];
}
Copy the code

/* By the way, the getter method for this tableView is exactly the same as in MVC, so I’m not going to write it separately. Speaking of getters, remember to write the getter for the viewModel!

Anyway, I’ll just post the code here, because I’m so responsible! * /

KVO callbacks and getters

#pragma mark - callback - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self.tableView reloadData]; } #pragma mark - getter - (UITableView *)tableView { if (! _tableView) { _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; _tableView.dataSource = self; _tableView.delegate = self; _tableView.tableFooterView = [[UIView alloc] init]; } return _tableView; } - (DHNewsViewModel *)viewModel { if (! _viewModel) { _viewModel = [[DHNewsViewModel alloc] init]; } return _viewModel; }Copy the code

Now, when you get to this point, all you have left is the tableView dataSource implementation.

Let’s implement the protocol method:

One at a time, the first is a method that returns how many rows of cells there are under a section.

When you implement this method, you’ll notice that, ah, the Controller seems to have a problem here again: how many rows of cells should depend on how many rows are in the dataList of our model.

Right, so now THAT I say Controller wants it, what should I do with the ViewModel? Give!

H in the ViewModel provides a method for the Controller to calculate how many rows of cells should be under the section group:

- (NSInteger)numberOfRowsInSection:(NSInteger)section;

Copy the code

This method is implemented according to the data hooks of the Model, so you’ll notice that it looks like all the data related code in the MVC Controller is implemented by the ViewModel instead of it.

- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
    return [self.model.dataList count];
}
Copy the code

Then in Controller, the implementation of this protocol method becomes:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.viewModel numberOfRowsInSection:section];
}
Copy the code

If you look at the MVC implementation a little bit, you’ll see that it actually ends up returning the count of the dataList in the Model, but the implementation changes from Controller to ViewModel.

Feel it?

There is one method left to return the cell. I’ll post the full code for the VM and Controller at the end, but I want you to try this method of returning the cell to interact with the VM yourself first.

Just remember that the VM gives the Controller what it wants (related to the Model).

Remember when I said at the beginning that the Controller’s presence was completely reduced? Take a look at the Controller code and see if it’s getting “stupid”. There’s no logic at all. It’s all tuning methods. That’s the kind of neat Controller we should have.

This example is actually relatively simple data processing, in real projects you will find that MVVM is quite easy to use to solve some problems, and later in the maintenance of bugs and add functionality is quite fast.

Afterword.

Use MVVM a few times and you will become very comfortable with its ideas, and I believe your object-oriented thinking will improve in the process.

Next you can try to implement a small demo using MVVM: The Controller uses a label to live out the current system time.

Tip: The Model is responsible for getting the system time, which is then stored in the property, the VM is responsible for converting it from NSDate to NSString, and the Controller is responsible for displaying it.

#import "ViewController.h"
#import "DHNewsViewModel.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView * tableView;
@property (nonatomic, strong) DHNewsViewModel * viewModel;

- (void)_registerObeserver;
- (void)_unregisterObserver;

@end

@implementation ViewController

- (void)dealloc
{
    [self _unregisterObserver];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.tableView];
    // 1, I want to request data
    [self.viewModel getData];
    // 2. I should receive a callback and refresh the interface with the model's latest data
    // Model and view should be synchronized at all times
    // KVO
    [self _registerObeserver];

}

#pragma mark - private methods
- (void)_registerObeserver
{
    [self.viewModel addObserver:self forKeyPath:[self.viewModel observingKeyPath] options:NSKeyValueObservingOptionNew context:nil];
}

- (void)_unregisterObserver
{
    [self.viewModel removeObserver:self forKeyPath:[self.viewModel observingKeyPath]];
}


#pragma mark - UITableViewProtocol
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.viewModel numberOfRowsInSection:section];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellIdf"];
    if(! cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellIdf"];
    }

    cell.textLabel.text = [self.viewModel cellTitleAtIndexPath:indexPath];
    cell.detailTextLabel.text = [self.viewModel cellDateAtIndexPath:indexPath];
    NSURL * imageUrl = [self.viewModel cellImageUrlAtIndexPath:indexPath];

    dispatch_async(dispatch_get_global_queue(0.0), ^{


        NSData * imageData = [NSData dataWithContentsOfURL:imageUrl];
        UIImage * image = [UIImage imageWithData:imageData];

        dispatch_async(dispatch_get_main_queue(), ^{
            cell.imageView.image = image;
        });

    });

    return cell;
}



#pragma mark - callback
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    [self.tableView reloadData];
}

#pragma mark - getter
- (UITableView *)tableView
{
    if(! _tableView) { _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; _tableView.dataSource = self; _tableView.delegate = self; _tableView.tableFooterView = [[UIView alloc] init]; }return _tableView;
}

- (DHNewsViewModel *)viewModel
{
    if(! _viewModel) { _viewModel = [[DHNewsViewModel alloc] init]; }return _viewModel;
}

@end
Copy the code

The complete code for ViewModel

The header file:

#import <Foundation/Foundation.h>

@interface DHNewsViewModel : NSObject

- (void)getData;

- (NSString *)observingKeyPath;


- (NSInteger)numberOfRowsInSection:(NSInteger)section;

- (NSString *)cellTitleAtIndexPath:(NSIndexPath *)indexPath;
- (NSString *)cellDateAtIndexPath:(NSIndexPath *)indexPath;
- (NSURL *)cellImageUrlAtIndexPath:(NSIndexPath *)indexPath;

- (NSString *)cellContentAtIndexPath:(NSIndexPath *)indexPath;


@end

Copy the code

Implementation file

#import "DHNewsViewModel.h"
#import "DHNewsModel.h"
#import <UIKit/UIKit.h>

@interface DHNewsViewModel ()

@property (nonatomic, strong) DHNewsModel * model;

- (NSDictionary *)_cellDicAtIndexPath:(NSIndexPath *)indexPath;

@end

@implementation DHNewsViewModel

#pragma mark - private methods
- (NSDictionary *)_cellDicAtIndexPath:(NSIndexPath *)indexPath
{
    return self.model.dataList[indexPath.row];
}

#pragma mark - interface methods
- (void)getData
{
    [self.model getData];
}

- (NSString *)observingKeyPath
{
    return @"model.dataList";
}


- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
    return [self.model.dataList count];
}

- (NSString *)cellTitleAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary * cellDic = [self _cellDicAtIndexPath:indexPath];
    return cellDic[@"title"];
}

- (NSString *)cellDateAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary * cellDic = [self _cellDicAtIndexPath:indexPath];
    return cellDic[@"date"];
}
- (NSURL *)cellImageUrlAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary * cellDic = [self _cellDicAtIndexPath:indexPath];
    return [NSURL URLWithString:cellDic[@"image"]];
}

- (NSString *)cellContentAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary * cellDic = [self _cellDicAtIndexPath:indexPath];
    return cellDic[@"content"];
}

#pragma mark - getter
- (DHNewsModel *)model
{
    if(! _model) { _model = [[DHNewsModel alloc] init]; }return _model;
}

@end
Copy the code