preface
Most developers start with an MVC architecture, then move on to MVCS, MVP, MVVM, etc.
In “take over an unqualified line of business code, I am how to maintain and reconstruct” I talked about some ideas and specific methods for the optimization of a project I received, in the actual chat page optimization process also need specific to operate the technical details of TableView view optimization, therefore, I’m going to give you a couple of examples of how you can reduce the load on a Controller for a TableView.
About some specific schemes and ideas of fluency optimization, I have described and implemented the Case in “exploration of iOS Performance Optimization”, you can refer to it. This article focuses on code structure optimization.
Optimize the overview
At the very beginning, faced with the huge amount of code of Controller, MY first choice was to use #pragma Mark-xxx to distinguish functions, which did solve the problem of dividing logic in Controller to a certain extent and improved the code readability. But it can’t solve the problem of huge amount of code. Later, I started to use categories for functional decoupling and partitioning, such as initializing other modules in the AppDelegate, decoupling code logic with the same functionality, and simplifying complex logic in the Controller. In the end, the above operations only reduce the amount of code in a class to a certain extent, but still do not achieve perfect simplification and layering of logic.
Complex Cell type TableView, I believe that as long as a certain amount of development experience will encounter a lot of. In this case, complicated business logic and numerous fields are mostly accompanied, which not only causes complicated Model logic, but also a large number of reused or unreused logic in Cell, and Controller is also filled with all kinds of if judgment logic. So the following are mainly for the complex Cell type of TableView optimization scheme.
Options available
I. Factory mode
Comparatively speaking, this approach is an immediate design pattern for IM chat interface, which has many types and relatively unified logic. For IM chat interface, the most complicated code is to deal with the problem of many chat card styles, because there are many card styles and the left and right display directions are highly inconsistent, resulting in a large number of code processing.
1) General idea of use
The Factory mode simplifies the above logic and abstracts the Cell creation and reuse process through the Factory. The Controller is only responsible for calling and transferring parameters, but not for creating or even reuse logic. This allows you to separate the two methods that deal with the most onerous business logic
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Copy the code
From the “if” judgment.
So the next step is to further simplify the extraction of the if judgment. We have several tools:
- object-oriented
- A subclass inherits
- agreement
- The map is stored
- cache
2) Component splitting idea
- Subclass inheritance makes perfect sense for IM lists. By encapsulating basic UI controls in the parent class and defining exposure methods, subclass cards can implement their business logic centrally, without having to deal with the same part of the business logic and UI layout repeatedly. And because a generic entry is defined in the parent class, cell parameters can be set directly in the factory class through the same API.
@protocol IMBaseCellDelegate <NSObject>
@end
@interface IMBaseCell : UITableViewCell
@property (nonatomic, weak) id<IMBaseCellDelegate> delegate;
@property (nonatomic, strong, readonly) IMMessage *message;
- (void)setupMessage:(IMMessage *)message;
+ (CGFloat)heightForMessage:(IMMessage *)message;
@end
Copy the code
-
Object oriented means that the View object handles its own business logic, and the Cell can calculate its own MessageContainerView height data through its own type of message object. After the calculation, it is returned to the Factory class for Cache storage, which can reduce the repeated calculation of message height, save time and resources, and improve fluency. Similar technologies are described in the iOS Performance Optimization Quest.
-
Of course, the use of Protocol is also important. Subclass cards usually have their own unique business logic. For example, message cards similar to wechat official numbers have multiple clickable areas. In this case, the delegate of the current card can be projected to the landing controller through protocol integration.
-
The role of map is basic. Because the types of IM messages are fixed, the message types can be abstractively mapped to corresponding cell classes, completely avoiding the lengthy judgment logic through if or switch.
+ (void)registerTableViewCells:(UITableView *)tableView{ @weakify(self) [[[self cellConfigurationMapper] allKeys] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { @strongify(self) kHGIMContentType messageType = (kHGIMContentType)[obj integerValue]; [tableView registerClass:[self cellClassForMessageType:messageType] forCellReuseIdentifier:[self cellIdentifierForMessageType:messageType]]; }]; } + (HGIMBaseCell *)dequeueReusableCellWithMessage:(IMMessage *)message forTableView:(UITableView *)tableView{ NSString *identifier = [self cellIdentifierForMessageType:(kHGIMContentType)message.type]; HGIMBaseCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; [cell setupMessage:message]; return cell; } + (NSDictionary *)cellConfigurationMapper { static NSDictionary *mapper = nil; if (! mapper) { mapper = @{ @(kIMContentText) : @{@"class" : [IMTextMessageCell class]}, @(kIMContentPicture) : @{@"class" : [IMImageMessageCell class]}, @(kIMContentMutiRichText) : @{@"class" : [IMMutiRichMessageCell class]}; } return mapper; }Copy the code
3) Deficiencies of Factory mode
The biggest advantage of Factory mode may lose its advantage in unsuitable business scenarios. As the name implies, Factory mode needs to be standardized and centralized. In case of some business scenarios with insufficient concentration, cell creation, inheritance and reuse can still be carried out through Factory mode. However, cells with less reuse and complex business logic will make the Protocol refresh and inheritance logic of the Cell out of control, and some business logic codes that can be centrally managed cannot be centrally processed.
Fat/Thin Model and MVCS
1) Fat/thin Model
Most people don’t know that models are differentiated, so HERE’s a quick introduction.
-
Fat Model
Because of the complexity of business logic and the need for weak coupling between the front and back ends, in most cases, the original data returned by the server side cannot be directly used by the View. Therefore, we can perform a series of operations when assigning values to the Model and call the original data after processing directly by calling the Get method. The advantage is that trivial data processing logic in Controller is reduced, so that Controller can focus on events and save a lot of time when Model is reused. After all, almost all the original data processing has been completed in Model, so there is no need to deal with each Controller one by one.
So the fat Model contains some of the weak business logic. The goal of the fat Model is that once the Controller gets the data from the fat Model, it can apply it directly to the View with very little or no extra action.
However, this is not perfect. When the reuse of cooked data is not high, or the Model has a binding coupling relationship with the specific business, the fat Model will cause problems. As the reuse increases, the code for different businesses will increase, and finally it is likely to be controlled.
Raw Data: timestamp:1234567 FatModel.h: @property (nonatomic, assign) CGFloat timestamp; - (NSString *)ymdDateString; // 2015-04-20 15:16 - (NSString *)gapString; // 3 minutes ago, 1 hour ago, 1 day ago, 2015-3-13 12:34 FATModel.m: - (NSString *)ymdDateString {// deal with timestamp 、、、、} - (NSString *)gapString {// deal with timestamp 、、、、}Copy the code
-
Thin Model
In contrast to the fat Model above, it is completely raw data assignment and does not do any processing. The processing logic is completely handled by the Controller or the data intermediate layer Adapter/Helper. Controller is cumbersome, and the processing code is likely to be reused, but with the middle layer, it is relatively easy, one adjustment, reuse everywhere. This ensures that the Model is as thin as possible, flexible enough for business use, and reduces the pressure of Controller. The same Model can adapt to different business needs.
However, it is also necessary to pay attention to the problems of mid-tier data conversion time and the need to maintain an additional utility class. If it is Cell data in TableView, whether to increase the Cache to reduce the time of multiple data conversion is a point to consider.
Raw Data:
"name":"casa",
"sex":"male",
SlimModel:
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *sex;
Helper:
#define Male 1;
#define Female 0;
+ (BOOL)sexWithString:(NSString *)sex;
Controller:
if ([Helper sexWithString:SlimModel.sex] == Male) {
...
}
Copy the code
2)MVCS
In MVCS, S stands for Store, which basically puts the operations of obtaining and processing data and converting data into a separate Store. Therefore, we can understand that the MVCS architecture is actually an extension of the thin Model. Store not only handles raw data, but also retrieves data. Therefore, the premise of MVCS is that it assumes that you are a thin Model, and data storage and processing are done by the Controller, which reduces the processing pressure of the Controller to some extent.
Apple itself adopts this kind of architecture idea, which, as can be seen from its name, is also a set of architecture derived from MVC. Conceptually, the part it splits is the Model part, a Store. But from a practical point of view, the Store is dedicated to data access, and it actually disassembles the logic in the Controller. Because not only the data storage part, but also the complex data processing part, including network requests and even the database and other complicated logic, are removed from the Controller, reducing the pressure of the Controller so that it can concentrate more on business logic.
Therefore, this architecture is relatively suitable for cases where controllers need to deal with very complex data logic. If the data logic is not that complex, it is not really suitable for this architecture design.
Third, the MVP
1) Brief introduction of architecture
In fact, the difference between MVC and MVP is not obvious, and MVP is a structure derived from MVC. The biggest difference between the two is that MVP uses Presenter to decoupler views and models, and they know nothing about each other. Communication is through presenters.
The P in MVP stands for Presenter, and literally C becomes P compared to MVC, so if you look more closely, not only Controller has changed, but the View has also changed.
The Presence of the Presenter layer isolates the direct communication between the View and Model layers. The View does not directly interact with the Model, but transmits data through the Presenter. A View is very thin and does not deploy any business logic. It is called a “Passive View”, meaning there is no initiative, whereas a Presenter is very thick and all the logic is deployed there.
As shown in the figure above, the essential difference with MVC is that MVC actions are handled by controllers and updated Model and View. ViewController is also a View class, so it has its own logic for presenters. The actual update logic is as follows:
The View, Presenter, and Model are decoupled through apis to reduce business coupling.
2) the advantages and disadvantages
- The advantages are obvious, the main is to extract complex logic from the Controller, reduce the pressure of the Controller and reduce the amount of code, so that the business logic is more direct, and it is easy to replace the View layer, even if the UI changes constantly, there will not be coupling with the business logic.
- The disadvantages are also obvious, for example, it will be troublesome to deal with the interaction and update of several horizontal views, and the introduction of Presenter will make the middle layer become very large, resulting in an increase in the amount of code, increasing the maintenance cost of simple pages.
Four, MVVM
As for MVVM application in iOS, I have discussed it in my previous article exploring MVVM design patterns in iOS development, which I will not repeat in this article.
Other plans
1) Protocol:
- IOS – Elegant code structure under multiple cells
- SigmaTableViewModel
2) Reduce the Controller size
- How to avoid big and clumsy UITableViewController on iOS?
The plan I adopted
My final approach is actually Factory +MVCS for two reasons: it fits the need for initial business decoupling and code isolation.
Project status and expectations
-
As a functional component of IM, the most complex position is mainly focused on data processing and UI logic. Among them, data processing belongs to the core logic, which involves the processing of long links, short connections and databases. This part is a complicated logic, which needs to be logically isolated and encapsulated. For the UI layer, it only needs to build the message body to send or to refresh the UI after receiving the message body, including the state update of the message body, expanding the complete message data and database processing, etc., which actually do not need to be concerned by the UI layer.
-
In the current project, long and short links, database processing and business logic are mixed together, requiring overall isolation and encapsulation. In addition to code isolation, the overall design should also be based on business logic, data processing logic, surface Api stability and component substitutability down.
3. Since there is a second phase development plan after the reconstruction, which is planned to be encapsulated into independent components, which are applied to both ends of C/B at the same time, it is necessary to design and develop componentization.
The Factory schema is roughly structured
+ (void)registerTableViewCells:(UITableView *)tableView{ @weakify(self) [[[self cellConfigurationMapper] allKeys] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { @strongify(self) kHGIMContentType messageType = (kHGIMContentType)[obj integerValue]; [tableView registerClass:[self cellClassForMessageType:messageType] forCellReuseIdentifier:[self cellIdentifierForMessageType:messageType]]; }]; } + (HGIMBaseCell *)dequeueReusableCellWithMessage:(IMMessage *)message forTableView:(UITableView *)tableView{ NSString *identifier = [self cellIdentifierForMessageType:(kHGIMContentType)message.type]; HGIMBaseCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; [cell setupMessage:message]; return cell; } + (NSDictionary *)cellConfigurationMapper { static NSDictionary *mapper = nil; if (! mapper) { mapper = @{ @(kIMContentText) : @{@"class" : [IMTextMessageCell class]}, @(kIMContentPicture) : @{@"class" : [IMImageMessageCell class]}, @(kIMContentMutiRichText) : @{@"class" : [IMMutiRichMessageCell class]}; } return mapper; }Copy the code
This part has been introduced a little bit above, the main purpose is to isolate initialization, reuse, Cell height calculation and so on from the Controller, standardize Cell calls through Cell subclass inheritance and protocol methods, and continuously put the whole TableView logic into the Cell.
Store- Data processing design structure
It can be seen from the simplified structure diagram that the main layers are 4 layers:
- Foundation layer: Mainly the core third-party framework layer.
- IMServer layer: mainly for the existing third-party framework for the initial interface encapsulation, including AFN call, some initialization logic of FMDB, initialization of long links and so on.
- IMAdapter layer: This layer is responsible for some encapsulation of the data structure based on the business logic and the project, including interface calls, long and short link processing, data storage processing and update, etc.
- Business data logic layer: This layer is relatively simple. It mainly encapsulates the business logic of group chat and private chat and exposes itself to clients by following Protocol respectively. UI layer directly calls components and attributes in Client to realize the respective business logic of private chat and group chat.
This design has two purposes: one is to realize the decoupling and coupling of the core logic and UI layer of components and the upper and lower components; the other is to continue to reduce the decoupling by separating the downward component encapsulation and the upward business function encapsulation after the stratigraphy of IMServer layer and IMAdapter layer.
Summary and reflection
In my opinion, no matter how the architecture is changed or how the structure and functions are redivided, everything is the improvement and functional segmentation of MVC architecture. Reducing the pressure of Controller, and then continuing to reduce the pressure of Cell, View and Model, is the process of constantly optimizing logic. In essence, Again, View, Model, and event processing scheduling are deployed in a variety of ways.
So, which design pattern should be used? My personal opinion is that the knife is in your hand, so use it as you need to!
Unused business modules cannot all apply a set of technical solutions and architectures, such as a simple list page. The architecture using MVVM is likely to require half more code than that using MVC, which is not cost-effective. So, need according to the specific business logic analysis, and then in a specific module or several complex business logic carried out while the page was optimized during the operation, and then select one of the most in line with the business logic and the future of the technical scheme of technical planning, it is not necessary to the whole project is to use an architecture, otherwise will let the whole rigid code.
As mentioned above, various design modes and load reduction methods are summarized around Model, View and Controller. In most cases, the first step is to split Controller, and then split and integrate the other two. Everything is operated by strictly tracking the data flow and lifecycle. So once you understand that, the rest will take care of itself.
Refrence
- De-model and data objects
- Handle complex Table Views in iOS with elegance
- MVC, MVP and MVVM Design Pattern
- Swift: How to Migrate MVC to MVVM & Intro Unit Testing
- MVCS – Model View Controller Store
- IOS application architecture on view layer organization and call scheme
- Brief introduction to MVC, MVP and MVVM architecture patterns
- What are MVP and MVC and what is the difference?